2 LuCI - Configuration Bind Interface
5 Offers an interface for binding configuration values to certain
6 data types. Supports value and range validation and basic dependencies.
12 Copyright 2008 Steven Barth <steven@midlink.org>
14 Licensed under the Apache License, Version 2.0 (the "License");
15 you may not use this file except in compliance with the License.
16 You may obtain a copy of the License at
18 http://www.apache.org/licenses/LICENSE-2.0
20 Unless required by applicable law or agreed to in writing, software
21 distributed under the License is distributed on an "AS IS" BASIS,
22 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 See the License for the specific language governing permissions and
24 limitations under the License.
27 module("luci.cbi", package.seeall)
29 require("luci.template")
30 local util = require("luci.util")
35 --local event = require "luci.sys.event"
36 local fs = require("nixio.fs")
37 local uci = require("luci.model.uci")
38 local class = util.class
39 local instanceof = util.instanceof
51 CREATE_PREFIX = "cbi.cts."
52 REMOVE_PREFIX = "cbi.rts."
54 -- Loads a CBI map from given file, creating an environment and returns it
55 function load(cbimap, ...)
56 local fs = require "nixio.fs"
57 local i18n = require "luci.i18n"
58 require("luci.config")
61 local upldir = "/lib/uci/upload/"
62 local cbidir = luci.util.libpath() .. "/model/cbi/"
64 assert(fs.stat(cbimap) or
65 fs.stat(cbidir..cbimap..".lua") or
66 fs.stat(cbidir..cbimap..".lua.gz"),
69 local func, err = loadfile(cbimap)
71 func, err = loadfile(cbidir..cbimap..".lua") or
72 loadfile(cbidir..cbimap..".lua.gz")
76 luci.i18n.loadc("cbi")
77 luci.i18n.loadc("uvl")
80 translate=i18n.translate,
81 translatef=i18n.translatef,
85 setfenv(func, setmetatable(env, {__index =
87 return rawget(tbl, key) or _M[key] or _G[key]
90 local maps = { func() }
92 local has_upload = false
94 for i, map in ipairs(maps) do
95 if not instanceof(map, Node) then
96 error("CBI map returns no valid map object!")
100 if map.upload_fields then
102 for _, field in ipairs(map.upload_fields) do
104 field.config .. '.' ..
105 field.section.sectiontype .. '.' ..
114 local uci = luci.model.uci.cursor()
115 local prm = luci.http.context.request.message.params
118 luci.http.setfilehandler(
119 function( field, chunk, eof )
120 if not field then return end
121 if field.name and not cbid then
122 local c, s, o = field.name:gmatch(
123 "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
126 if c and s and o then
127 local t = uci:get( c, s )
128 if t and uploads[c.."."..t.."."..o] then
129 local path = upldir .. field.name
130 fd = io.open(path, "w")
139 if field.name == cbid and fd then
155 local function _uvl_validate_section(node, name)
156 local co = node.map:get()
158 luci.uvl.STRICT_UNKNOWN_OPTIONS = false
159 luci.uvl.STRICT_UNKNOWN_SECTIONS = false
161 local function tag_fields(e)
162 if e.option and node.fields[e.option] then
163 if node.fields[e.option].error then
164 node.fields[e.option].error[name] = e
166 node.fields[e.option].error = { [name] = e }
169 for _, c in ipairs(e.childs) do tag_fields(c) end
173 local function tag_section(e)
175 for _, c in ipairs(e.childs or { e }) do
176 if c.childs and not c:is(luci.uvl.errors.ERR_DEPENDENCY) then
177 table.insert( s, c.childs[1]:string() )
179 table.insert( s, c:string() )
186 node.error = { [name] = s }
191 local stat, err = node.map.validator:validate_section(node.config, name, co)
193 node.map.save = false
200 local function _uvl_strip_remote_dependencies(deps)
203 for k, v in pairs(deps) do
204 k = k:gsub("%$config%.%$section%.", "")
205 if k:match("^[%w_]+$") and type(v) == "string" then
214 -- Node pseudo abstract class
217 function Node.__init__(self, title, description)
219 self.title = title or ""
220 self.description = description or ""
221 self.template = "cbi/node"
225 function Node._i18n(self, config, section, option, title, description)
228 if type(luci.i18n) == "table" then
230 local key = config and config:gsub("[^%w]+", "") or ""
232 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
233 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
235 self.title = title or luci.i18n.translate( key, option or section or config )
236 self.description = description or luci.i18n.translate( key .. "_desc", "" )
241 function Node.prepare(self, ...)
242 for k, child in ipairs(self.children) do
247 -- Append child nodes
248 function Node.append(self, obj)
249 table.insert(self.children, obj)
252 -- Parse this node and its children
253 function Node.parse(self, ...)
254 for k, child in ipairs(self.children) do
260 function Node.render(self, scope)
264 luci.template.render(self.template, scope)
267 -- Render the children
268 function Node.render_children(self, ...)
269 for k, node in ipairs(self.children) do
276 A simple template element
278 Template = class(Node)
280 function Template.__init__(self, template)
282 self.template = template
285 function Template.render(self)
286 luci.template.render(self.template, {self=self})
289 function Template.parse(self, readinput)
290 self.readinput = (readinput ~= false)
291 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
296 Map - A map describing a configuration file
300 function Map.__init__(self, config, ...)
301 Node.__init__(self, ...)
302 Node._i18n(self, config, nil, nil, ...)
305 self.parsechain = {self.config}
306 self.template = "cbi/map"
307 self.apply_on_parse = nil
308 self.readinput = true
312 self.uci = uci.cursor()
317 if not self.uci:load(self.config) then
318 error("Unable to read UCI data: " .. self.config)
321 self.validator = luci.uvl.UVL()
322 self.scheme = self.validator:get_scheme(self.config)
326 function Map.formvalue(self, key)
327 return self.readinput and luci.http.formvalue(key)
330 function Map.formvaluetable(self, key)
331 return self.readinput and luci.http.formvaluetable(key) or {}
334 function Map.get_scheme(self, sectiontype, option)
336 return self.scheme and self.scheme.sections[sectiontype]
338 return self.scheme and self.scheme.variables[sectiontype]
339 and self.scheme.variables[sectiontype][option]
343 function Map.submitstate(self)
344 return self:formvalue("cbi.submit")
347 -- Chain foreign config
348 function Map.chain(self, config)
349 table.insert(self.parsechain, config)
352 function Map.state_handler(self, state)
356 -- Use optimized UCI writing
357 function Map.parse(self, readinput, ...)
358 self.readinput = (readinput ~= false)
360 if self:formvalue("cbi.skip") then
361 self.state = FORM_SKIP
362 return self:state_handler(self.state)
365 Node.parse(self, ...)
368 for i, config in ipairs(self.parsechain) do
369 self.uci:save(config)
371 if self:submitstate() and not self.proceed and (self.flow.autoapply or luci.http.formvalue("cbi.apply")) then
372 for i, config in ipairs(self.parsechain) do
373 self.uci:commit(config)
375 -- Refresh data because commit changes section names
376 self.uci:load(config)
378 if self.apply_on_parse then
379 self.uci:apply(self.parsechain)
381 self._apply = function()
382 local cmd = self.uci:apply(self.parsechain, true)
388 Node.parse(self, true)
391 for i, config in ipairs(self.parsechain) do
392 self.uci:unload(config)
394 if type(self.commit_handler) == "function" then
395 self:commit_handler(self:submitstate())
399 if self:submitstate() then
400 if not self.save then
401 self.state = FORM_INVALID
402 elseif self.proceed then
403 self.state = FORM_PROCEED
405 self.state = self.changed and FORM_CHANGED or FORM_VALID
408 self.state = FORM_NODATA
411 return self:state_handler(self.state)
414 function Map.render(self, ...)
415 Node.render(self, ...)
417 local fp = self._apply()
423 -- Creates a child section
424 function Map.section(self, class, ...)
425 if instanceof(class, AbstractSection) then
426 local obj = class(self, ...)
430 error("class must be a descendent of AbstractSection")
435 function Map.add(self, sectiontype)
436 return self.uci:add(self.config, sectiontype)
440 function Map.set(self, section, option, value)
442 return self.uci:set(self.config, section, option, value)
444 return self.uci:set(self.config, section, value)
449 function Map.del(self, section, option)
451 return self.uci:delete(self.config, section, option)
453 return self.uci:delete(self.config, section)
458 function Map.get(self, section, option)
460 return self.uci:get_all(self.config)
462 return self.uci:get(self.config, section, option)
464 return self.uci:get_all(self.config, section)
471 Compound = class(Node)
473 function Compound.__init__(self, ...)
475 self.template = "cbi/compound"
476 self.children = {...}
479 function Compound.populate_delegator(self, delegator)
480 for _, v in ipairs(self.children) do
481 v.delegator = delegator
485 function Compound.parse(self, ...)
486 local cstate, state = 0
488 for k, child in ipairs(self.children) do
489 cstate = child:parse(...)
490 state = (not state or cstate < state) and cstate or state
498 Delegator - Node controller
500 Delegator = class(Node)
501 function Delegator.__init__(self, ...)
502 Node.__init__(self, ...)
504 self.defaultpath = {}
505 self.pageaction = false
506 self.readinput = true
507 self.allow_reset = false
508 self.allow_back = false
509 self.allow_finish = false
510 self.template = "cbi/delegator"
513 function Delegator.set(self, name, node)
514 if type(node) == "table" and getmetatable(node) == nil then
515 node = Compound(unpack(node))
517 assert(type(node) == "function" or instanceof(node, Compound), "Invalid")
518 assert(not self.nodes[name], "Duplicate entry")
520 self.nodes[name] = node
523 function Delegator.add(self, name, node)
524 node = self:set(name, node)
525 self.defaultpath[#self.defaultpath+1] = name
528 function Delegator.insert_after(self, name, after)
529 local n = #self.chain
530 for k, v in ipairs(self.chain) do
536 table.insert(self.chain, n, name)
539 function Delegator.set_route(self, ...)
540 local n, chain, route = 0, self.chain, {...}
542 if chain[i] == self.current then
551 for i = n + 1, #chain do
556 function Delegator.get(self, name)
557 return self.nodes[name]
560 function Delegator.parse(self, ...)
562 self.chain = self.chain or self:get_chain()
563 self.current = self.current or self:get_active()
564 self.active = self.active or self:get(self.current)
565 assert(self.active, "Invalid state")
567 local stat = FORM_DONE
568 if type(self.active) ~= "function" then
569 self.active:populate_delegator(self)
570 stat = self.active:parse()
575 if stat > FORM_PROCEED then
576 if Map.formvalue(self, "cbi.delg.back") then
577 newcurrent = self:get_prev(self.current)
579 newcurrent = self:get_next(self.current)
581 elseif stat < FORM_PROCEED then
586 if not Map.formvalue(self, "cbi.submit") then
588 elseif not newcurrent or not self:get(newcurrent) then
591 self.current = newcurrent
592 self.active = self:get(self.current)
593 if type(self.active) ~= "function" then
594 self.active:parse(false)
597 return self:parse(...)
602 function Delegator.get_next(self, state)
603 for k, v in ipairs(self.chain) do
605 return self.chain[k+1]
610 function Delegator.get_prev(self, state)
611 for k, v in ipairs(self.chain) do
613 return self.chain[k-1]
618 function Delegator.get_chain(self)
619 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
620 return type(x) == "table" and x or {x}
623 function Delegator.get_active(self)
624 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
632 Page.__init__ = Node.__init__
633 Page.parse = function() end
637 SimpleForm - A Simple non-UCI form
639 SimpleForm = class(Node)
641 function SimpleForm.__init__(self, config, title, description, data)
642 Node.__init__(self, title, description)
644 self.data = data or {}
645 self.template = "cbi/simpleform"
647 self.pageaction = false
648 self.readinput = true
651 SimpleForm.formvalue = Map.formvalue
652 SimpleForm.formvaluetable = Map.formvaluetable
654 function SimpleForm.parse(self, readinput, ...)
655 self.readinput = (readinput ~= false)
657 if self:formvalue("cbi.skip") then
661 if self:submitstate() then
662 Node.parse(self, 1, ...)
666 for k, j in ipairs(self.children) do
667 for i, v in ipairs(j.children) do
669 and (not v.tag_missing or not v.tag_missing[1])
670 and (not v.tag_invalid or not v.tag_invalid[1])
676 not self:submitstate() and FORM_NODATA
677 or valid and FORM_VALID
680 self.dorender = not self.handle
682 local nrender, nstate = self:handle(state, self.data)
683 self.dorender = self.dorender or (nrender ~= false)
684 state = nstate or state
689 function SimpleForm.render(self, ...)
690 if self.dorender then
691 Node.render(self, ...)
695 function SimpleForm.submitstate(self)
696 return self:formvalue("cbi.submit")
699 function SimpleForm.section(self, class, ...)
700 if instanceof(class, AbstractSection) then
701 local obj = class(self, ...)
705 error("class must be a descendent of AbstractSection")
709 -- Creates a child field
710 function SimpleForm.field(self, class, ...)
712 for k, v in ipairs(self.children) do
713 if instanceof(v, SimpleSection) then
719 section = self:section(SimpleSection)
722 if instanceof(class, AbstractValue) then
723 local obj = class(self, section, ...)
724 obj.track_missing = true
728 error("class must be a descendent of AbstractValue")
732 function SimpleForm.set(self, section, option, value)
733 self.data[option] = value
737 function SimpleForm.del(self, section, option)
738 self.data[option] = nil
742 function SimpleForm.get(self, section, option)
743 return self.data[option]
747 function SimpleForm.get_scheme()
752 Form = class(SimpleForm)
754 function Form.__init__(self, ...)
755 SimpleForm.__init__(self, ...)
763 AbstractSection = class(Node)
765 function AbstractSection.__init__(self, map, sectiontype, ...)
766 Node.__init__(self, ...)
767 self.sectiontype = sectiontype
769 self.config = map.config
774 self.tag_invalid = {}
775 self.tag_deperror = {}
779 self.addremove = false
783 -- Appends a new option
784 function AbstractSection.option(self, class, option, ...)
785 -- Autodetect from UVL
786 if class == true and self.map:get_scheme(self.sectiontype, option) then
787 local vs = self.map:get_scheme(self.sectiontype, option)
788 if vs.type == "boolean" then
790 elseif vs.type == "list" then
792 elseif vs.type == "enum" or vs.type == "reference" then
799 if instanceof(class, AbstractValue) then
800 local obj = class(self.map, self, option, ...)
802 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
805 self.fields[option] = obj
807 elseif class == true then
808 error("No valid class was given and autodetection failed.")
810 error("class must be a descendant of AbstractValue")
814 -- Parse optional options
815 function AbstractSection.parse_optionals(self, section)
816 if not self.optional then
820 self.optionals[section] = {}
822 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
823 for k,v in ipairs(self.children) do
824 if v.optional and not v:cfgvalue(section) then
825 if field == v.option then
827 self.map.proceed = true
829 table.insert(self.optionals[section], v)
834 if field and #field > 0 and self.dynamic then
835 self:add_dynamic(field)
839 -- Add a dynamic option
840 function AbstractSection.add_dynamic(self, field, optional)
841 local o = self:option(Value, field, field)
842 o.optional = optional
845 -- Parse all dynamic options
846 function AbstractSection.parse_dynamic(self, section)
847 if not self.dynamic then
851 local arr = luci.util.clone(self:cfgvalue(section))
852 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
853 for k, v in pairs(form) do
857 for key,val in pairs(arr) do
860 for i,c in ipairs(self.children) do
861 if c.option == key then
866 if create and key:sub(1, 1) ~= "." then
867 self.map.proceed = true
868 self:add_dynamic(key, true)
873 -- Returns the section's UCI table
874 function AbstractSection.cfgvalue(self, section)
875 return self.map:get(section)
879 function AbstractSection.push_events(self)
880 --luci.util.append(self.map.events, self.events)
881 self.map.changed = true
884 -- Removes the section
885 function AbstractSection.remove(self, section)
886 self.map.proceed = true
887 return self.map:del(section)
890 -- Creates the section
891 function AbstractSection.create(self, section)
895 stat = section:match("^%w+$") and self.map:set(section, nil, self.sectiontype)
897 section = self.map:add(self.sectiontype)
902 for k,v in pairs(self.children) do
904 self.map:set(section, v.option, v.default)
908 for k,v in pairs(self.defaults) do
909 self.map:set(section, k, v)
913 self.map.proceed = true
919 SimpleSection = class(AbstractSection)
921 function SimpleSection.__init__(self, form, ...)
922 AbstractSection.__init__(self, form, nil, ...)
923 self.template = "cbi/nullsection"
927 Table = class(AbstractSection)
929 function Table.__init__(self, form, data, ...)
930 local datasource = {}
932 datasource.config = "table"
933 self.data = data or {}
935 datasource.formvalue = Map.formvalue
936 datasource.formvaluetable = Map.formvaluetable
937 datasource.readinput = true
939 function datasource.get(self, section, option)
940 return tself.data[section] and tself.data[section][option]
943 function datasource.submitstate(self)
944 return Map.formvalue(self, "cbi.submit")
947 function datasource.del(...)
951 function datasource.get_scheme()
955 AbstractSection.__init__(self, datasource, "table", ...)
956 self.template = "cbi/tblsection"
957 self.rowcolors = true
958 self.anonymous = true
961 function Table.parse(self, readinput)
962 self.map.readinput = (readinput ~= false)
963 for i, k in ipairs(self:cfgsections()) do
964 if self.map:submitstate() then
970 function Table.cfgsections(self)
973 for i, v in luci.util.kspairs(self.data) do
974 table.insert(sections, i)
980 function Table.update(self, data)
987 NamedSection - A fixed configuration section defined by its name
989 NamedSection = class(AbstractSection)
991 function NamedSection.__init__(self, map, section, stype, ...)
992 AbstractSection.__init__(self, map, stype, ...)
993 Node._i18n(self, map.config, section, nil, ...)
996 self.addremove = false
998 -- Use defaults from UVL
999 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1000 local vs = self.map:get_scheme(self.sectiontype)
1001 self.addremove = not vs.unique and not vs.required
1002 self.dynamic = vs.dynamic
1003 self.title = self.title or vs.title
1004 self.description = self.description or vs.descr
1007 self.template = "cbi/nsection"
1008 self.section = section
1011 function NamedSection.parse(self, novld)
1012 local s = self.section
1013 local active = self:cfgvalue(s)
1015 if self.addremove then
1016 local path = self.config.."."..s
1017 if active then -- Remove the section
1018 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1022 else -- Create and apply default values
1023 if self.map:formvalue("cbi.cns."..path) then
1031 AbstractSection.parse_dynamic(self, s)
1032 if self.map:submitstate() then
1035 if not novld and not self.override_scheme and self.map.scheme then
1036 _uvl_validate_section(self, s)
1039 AbstractSection.parse_optionals(self, s)
1041 if self.changed then
1049 TypedSection - A (set of) configuration section(s) defined by the type
1050 addremove: Defines whether the user can add/remove sections of this type
1051 anonymous: Allow creating anonymous sections
1052 validate: a validation function returning nil if the section is invalid
1054 TypedSection = class(AbstractSection)
1056 function TypedSection.__init__(self, map, type, ...)
1057 AbstractSection.__init__(self, map, type, ...)
1058 Node._i18n(self, map.config, type, nil, ...)
1060 self.template = "cbi/tsection"
1062 self.anonymous = false
1064 -- Use defaults from UVL
1065 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1066 local vs = self.map:get_scheme(self.sectiontype)
1067 self.addremove = not vs.unique and not vs.required
1068 self.dynamic = vs.dynamic
1069 self.anonymous = not vs.named
1070 self.title = self.title or vs.title
1071 self.description = self.description or vs.descr
1075 -- Return all matching UCI sections for this TypedSection
1076 function TypedSection.cfgsections(self)
1078 self.map.uci:foreach(self.map.config, self.sectiontype,
1080 if self:checkscope(section[".name"]) then
1081 table.insert(sections, section[".name"])
1088 -- Limits scope to sections that have certain option => value pairs
1089 function TypedSection.depends(self, option, value)
1090 table.insert(self.deps, {option=option, value=value})
1093 function TypedSection.parse(self, novld)
1094 if self.addremove then
1096 local crval = REMOVE_PREFIX .. self.config
1097 local name = self.map:formvaluetable(crval)
1098 for k,v in pairs(name) do
1099 if k:sub(-2) == ".x" then
1100 k = k:sub(1, #k - 2)
1102 if self:cfgvalue(k) and self:checkscope(k) then
1109 for i, k in ipairs(self:cfgsections()) do
1110 AbstractSection.parse_dynamic(self, k)
1111 if self.map:submitstate() then
1112 Node.parse(self, k, novld)
1114 if not novld and not self.override_scheme and self.map.scheme then
1115 _uvl_validate_section(self, k)
1118 AbstractSection.parse_optionals(self, k)
1121 if self.addremove then
1124 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1125 local name = self.map:formvalue(crval)
1126 if self.anonymous then
1128 created = self:create()
1132 -- Ignore if it already exists
1133 if self:cfgvalue(name) then
1137 name = self:checkscope(name)
1140 self.err_invalid = true
1143 if name and #name > 0 then
1144 created = self:create(name) and name
1146 self.invalid_cts = true
1153 AbstractSection.parse_optionals(self, created)
1157 if created or self.changed then
1162 -- Verifies scope of sections
1163 function TypedSection.checkscope(self, section)
1164 -- Check if we are not excluded
1165 if self.filter and not self:filter(section) then
1169 -- Check if at least one dependency is met
1170 if #self.deps > 0 and self:cfgvalue(section) then
1173 for k, v in ipairs(self.deps) do
1174 if self:cfgvalue(section)[v.option] == v.value then
1184 return self:validate(section)
1188 -- Dummy validate function
1189 function TypedSection.validate(self, section)
1195 AbstractValue - An abstract Value Type
1196 null: Value can be empty
1197 valid: A function returning the value if it is valid otherwise nil
1198 depends: A table of option => value pairs of which one must be true
1199 default: The default value
1200 size: The size of the input fields
1201 rmempty: Unset value if empty
1202 optional: This value is optional (see AbstractSection.optionals)
1204 AbstractValue = class(Node)
1206 function AbstractValue.__init__(self, map, section, option, ...)
1207 Node.__init__(self, ...)
1208 self.section = section
1209 self.option = option
1211 self.config = map.config
1212 self.tag_invalid = {}
1213 self.tag_missing = {}
1214 self.tag_reqerror = {}
1217 --self.cast = "string"
1219 self.track_missing = false
1223 self.optional = false
1226 function AbstractValue.prepare(self)
1227 -- Use defaults from UVL
1228 if not self.override_scheme
1229 and self.map:get_scheme(self.section.sectiontype, self.option) then
1230 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1231 if self.cast == nil then
1232 self.cast = (vs.type == "list") and "list" or "string"
1234 self.title = self.title or vs.title
1235 self.description = self.description or vs.descr
1236 if self.default == nil then
1237 self.default = vs.default
1240 if vs.depends and not self.override_dependencies then
1241 for i, deps in ipairs(vs.depends) do
1242 deps = _uvl_strip_remote_dependencies(deps)
1250 self.cast = self.cast or "string"
1253 -- Add a dependencie to another section field
1254 function AbstractValue.depends(self, field, value)
1256 if type(field) == "string" then
1263 table.insert(self.deps, {deps=deps, add=""})
1266 -- Generates the unique CBID
1267 function AbstractValue.cbid(self, section)
1268 return "cbid."..self.map.config.."."..section.."."..self.option
1271 -- Return whether this object should be created
1272 function AbstractValue.formcreated(self, section)
1273 local key = "cbi.opt."..self.config.."."..section
1274 return (self.map:formvalue(key) == self.option)
1277 -- Returns the formvalue for this object
1278 function AbstractValue.formvalue(self, section)
1279 return self.map:formvalue(self:cbid(section))
1282 function AbstractValue.additional(self, value)
1283 self.optional = value
1286 function AbstractValue.mandatory(self, value)
1287 self.rmempty = not value
1290 function AbstractValue.parse(self, section, novld)
1291 local fvalue = self:formvalue(section)
1292 local cvalue = self:cfgvalue(section)
1294 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1295 fvalue = self:transform(self:validate(fvalue, section))
1296 if not fvalue and not novld then
1298 self.error[section] = "invalid"
1300 self.error = { [section] = "invalid" }
1302 if self.section.error then
1303 table.insert(self.section.error[section], "invalid")
1305 self.section.error = {[section] = {"invalid"}}
1307 self.map.save = false
1309 if fvalue and not (fvalue == cvalue) then
1310 if self:write(section, fvalue) then
1312 self.section.changed = true
1313 --luci.util.append(self.map.events, self.events)
1316 else -- Unset the UCI or error
1317 if self.rmempty or self.optional then
1318 if self:remove(section) then
1320 self.section.changed = true
1321 --luci.util.append(self.map.events, self.events)
1323 elseif cvalue ~= fvalue and not novld then
1324 self:write(section, fvalue or "")
1326 self.error[section] = "missing"
1328 self.error = { [section] = "missing" }
1330 self.map.save = false
1335 -- Render if this value exists or if it is mandatory
1336 function AbstractValue.render(self, s, scope)
1337 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1340 scope.cbid = self:cbid(s)
1341 scope.striptags = luci.util.striptags
1343 scope.ifattr = function(cond,key,val)
1345 return string.format(
1346 ' %s="%s"', tostring(key),
1347 luci.util.pcdata(tostring( val
1349 or (type(self[key]) ~= "function" and self[key])
1357 scope.attr = function(...)
1358 return scope.ifattr( true, ... )
1361 Node.render(self, scope)
1365 -- Return the UCI value of this object
1366 function AbstractValue.cfgvalue(self, section)
1367 local value = self.map:get(section, self.option)
1370 elseif not self.cast or self.cast == type(value) then
1372 elseif self.cast == "string" then
1373 if type(value) == "table" then
1376 elseif self.cast == "table" then
1377 return luci.util.split(value, "%s+", nil, true)
1381 -- Validate the form value
1382 function AbstractValue.validate(self, value)
1386 AbstractValue.transform = AbstractValue.validate
1390 function AbstractValue.write(self, section, value)
1391 return self.map:set(section, self.option, value)
1395 function AbstractValue.remove(self, section)
1396 return self.map:del(section, self.option)
1403 Value - A one-line value
1404 maxlength: The maximum length
1406 Value = class(AbstractValue)
1408 function Value.__init__(self, ...)
1409 AbstractValue.__init__(self, ...)
1410 self.template = "cbi/value"
1415 function Value.value(self, key, val)
1417 table.insert(self.keylist, tostring(key))
1418 table.insert(self.vallist, tostring(val))
1422 -- DummyValue - This does nothing except being there
1423 DummyValue = class(AbstractValue)
1425 function DummyValue.__init__(self, ...)
1426 AbstractValue.__init__(self, ...)
1427 self.template = "cbi/dvalue"
1431 function DummyValue.cfgvalue(self, section)
1434 if type(self.value) == "function" then
1435 value = self:value(section)
1440 value = AbstractValue.cfgvalue(self, section)
1445 function DummyValue.parse(self)
1451 Flag - A flag being enabled or disabled
1453 Flag = class(AbstractValue)
1455 function Flag.__init__(self, ...)
1456 AbstractValue.__init__(self, ...)
1457 self.template = "cbi/fvalue"
1463 -- A flag can only have two states: set or unset
1464 function Flag.parse(self, section)
1465 local fvalue = self:formvalue(section)
1468 fvalue = self.enabled
1470 fvalue = self.disabled
1473 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1474 if not(fvalue == self:cfgvalue(section)) then
1475 self:write(section, fvalue)
1478 self:remove(section)
1485 ListValue - A one-line value predefined in a list
1486 widget: The widget that will be used (select, radio)
1488 ListValue = class(AbstractValue)
1490 function ListValue.__init__(self, ...)
1491 AbstractValue.__init__(self, ...)
1492 self.template = "cbi/lvalue"
1497 self.widget = "select"
1500 function ListValue.prepare(self, ...)
1501 AbstractValue.prepare(self, ...)
1502 if not self.override_scheme
1503 and self.map:get_scheme(self.section.sectiontype, self.option) then
1504 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1505 if self.value and vs.valuelist and not self.override_values then
1506 for k, v in ipairs(vs.valuelist) do
1508 if not self.override_dependencies
1509 and vs.enum_depends and vs.enum_depends[v.value] then
1510 for i, dep in ipairs(vs.enum_depends[v.value]) do
1511 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1514 self:value(v.value, v.title or v.value, unpack(deps))
1520 function ListValue.value(self, key, val, ...)
1521 if luci.util.contains(self.keylist, key) then
1526 table.insert(self.keylist, tostring(key))
1527 table.insert(self.vallist, tostring(val))
1529 for i, deps in ipairs({...}) do
1530 table.insert(self.deps, {add = "-"..key, deps=deps})
1534 function ListValue.validate(self, val)
1535 if luci.util.contains(self.keylist, val) then
1545 MultiValue - Multiple delimited values
1546 widget: The widget that will be used (select, checkbox)
1547 delimiter: The delimiter that will separate the values (default: " ")
1549 MultiValue = class(AbstractValue)
1551 function MultiValue.__init__(self, ...)
1552 AbstractValue.__init__(self, ...)
1553 self.template = "cbi/mvalue"
1558 self.widget = "checkbox"
1559 self.delimiter = " "
1562 function MultiValue.render(self, ...)
1563 if self.widget == "select" and not self.size then
1564 self.size = #self.vallist
1567 AbstractValue.render(self, ...)
1570 function MultiValue.value(self, key, val)
1571 if luci.util.contains(self.keylist, key) then
1576 table.insert(self.keylist, tostring(key))
1577 table.insert(self.vallist, tostring(val))
1580 function MultiValue.valuelist(self, section)
1581 local val = self:cfgvalue(section)
1583 if not(type(val) == "string") then
1587 return luci.util.split(val, self.delimiter)
1590 function MultiValue.validate(self, val)
1591 val = (type(val) == "table") and val or {val}
1595 for i, value in ipairs(val) do
1596 if luci.util.contains(self.keylist, value) then
1597 result = result and (result .. self.delimiter .. value) or value
1605 StaticList = class(MultiValue)
1607 function StaticList.__init__(self, ...)
1608 MultiValue.__init__(self, ...)
1610 self.valuelist = self.cfgvalue
1612 if not self.override_scheme
1613 and self.map:get_scheme(self.section.sectiontype, self.option) then
1614 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1615 if self.value and vs.values and not self.override_values then
1616 for k, v in pairs(vs.values) do
1623 function StaticList.validate(self, value)
1624 value = (type(value) == "table") and value or {value}
1627 for i, v in ipairs(value) do
1628 if luci.util.contains(self.keylist, v) then
1629 table.insert(valid, v)
1636 DynamicList = class(AbstractValue)
1638 function DynamicList.__init__(self, ...)
1639 AbstractValue.__init__(self, ...)
1640 self.template = "cbi/dynlist"
1646 function DynamicList.value(self, key, val)
1648 table.insert(self.keylist, tostring(key))
1649 table.insert(self.vallist, tostring(val))
1652 function DynamicList.write(self, ...)
1653 self.map.proceed = true
1654 return AbstractValue.write(self, ...)
1657 function DynamicList.formvalue(self, section)
1658 local value = AbstractValue.formvalue(self, section)
1659 value = (type(value) == "table") and value or {value}
1662 for i, v in ipairs(value) do
1664 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1665 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1666 table.insert(valid, v)
1675 TextValue - A multi-line value
1678 TextValue = class(AbstractValue)
1680 function TextValue.__init__(self, ...)
1681 AbstractValue.__init__(self, ...)
1682 self.template = "cbi/tvalue"
1688 Button = class(AbstractValue)
1690 function Button.__init__(self, ...)
1691 AbstractValue.__init__(self, ...)
1692 self.template = "cbi/button"
1693 self.inputstyle = nil
1698 FileUpload = class(AbstractValue)
1700 function FileUpload.__init__(self, ...)
1701 AbstractValue.__init__(self, ...)
1702 self.template = "cbi/upload"
1703 if not self.map.upload_fields then
1704 self.map.upload_fields = { self }
1706 self.map.upload_fields[#self.map.upload_fields+1] = self
1710 function FileUpload.formcreated(self, section)
1711 return AbstractValue.formcreated(self, section) or
1712 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1713 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1716 function FileUpload.cfgvalue(self, section)
1717 local val = AbstractValue.cfgvalue(self, section)
1718 if val and fs.access(val) then
1724 function FileUpload.formvalue(self, section)
1725 local val = AbstractValue.formvalue(self, section)
1727 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1728 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1738 function FileUpload.remove(self, section)
1739 local val = AbstractValue.formvalue(self, section)
1740 if val and fs.access(val) then fs.unlink(val) end
1741 return AbstractValue.remove(self, section)
1745 FileBrowser = class(AbstractValue)
1747 function FileBrowser.__init__(self, ...)
1748 AbstractValue.__init__(self, ...)
1749 self.template = "cbi/browser"