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/"
65 if fs.access(cbidir..cbimap..".lua") then
66 func, err = loadfile(cbidir..cbimap..".lua")
67 elseif fs.access(cbimap) then
68 func, err = loadfile(cbimap)
70 func, err = nil, "Model '" .. cbimap .. "' not found!"
75 luci.i18n.loadc("base")
78 translate=i18n.translate,
79 translatef=i18n.translatef,
83 setfenv(func, setmetatable(env, {__index =
85 return rawget(tbl, key) or _M[key] or _G[key]
88 local maps = { func() }
90 local has_upload = false
92 for i, map in ipairs(maps) do
93 if not instanceof(map, Node) then
94 error("CBI map returns no valid map object!")
98 if map.upload_fields then
100 for _, field in ipairs(map.upload_fields) do
102 field.config .. '.' ..
103 field.section.sectiontype .. '.' ..
112 local uci = luci.model.uci.cursor()
113 local prm = luci.http.context.request.message.params
116 luci.http.setfilehandler(
117 function( field, chunk, eof )
118 if not field then return end
119 if field.name and not cbid then
120 local c, s, o = field.name:gmatch(
121 "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
124 if c and s and o then
125 local t = uci:get( c, s )
126 if t and uploads[c.."."..t.."."..o] then
127 local path = upldir .. field.name
128 fd = io.open(path, "w")
137 if field.name == cbid and fd then
153 local function _uvl_validate_section(node, name)
154 local co = node.map:get()
156 luci.uvl.STRICT_UNKNOWN_OPTIONS = false
157 luci.uvl.STRICT_UNKNOWN_SECTIONS = false
159 local function tag_fields(e)
160 if e.option and node.fields[e.option] then
161 if node.fields[e.option].error then
162 node.fields[e.option].error[name] = e
164 node.fields[e.option].error = { [name] = e }
167 for _, c in ipairs(e.childs) do tag_fields(c) end
171 local function tag_section(e)
173 for _, c in ipairs(e.childs or { e }) do
174 if c.childs and not c:is('DEPENDENCY') then
175 table.insert( s, c.childs[1]:string() )
177 table.insert( s, c:string() )
184 node.error = { [name] = s }
189 local stat, err = node.map.validator:validate_section(node.config, name, co)
191 node.map.save = false
198 local function _uvl_strip_remote_dependencies(deps)
201 for k, v in pairs(deps) do
202 k = k:gsub("%$config%.%$section%.", "")
203 if k:match("^[%w_]+$") and type(v) == "string" then
212 -- Node pseudo abstract class
215 function Node.__init__(self, title, description)
217 self.title = title or ""
218 self.description = description or ""
219 self.template = "cbi/node"
223 function Node._run_hook(self, hook)
224 if type(self[hook]) == "function" then
225 return self[hook](self)
229 function Node._run_hooks(self, ...)
232 for _, f in ipairs(arg) do
233 if type(self[f]) == "function" then
242 function Node.prepare(self, ...)
243 for k, child in ipairs(self.children) do
248 -- Append child nodes
249 function Node.append(self, obj)
250 table.insert(self.children, obj)
253 -- Parse this node and its children
254 function Node.parse(self, ...)
255 for k, child in ipairs(self.children) do
261 function Node.render(self, scope)
265 luci.template.render(self.template, scope)
268 -- Render the children
269 function Node.render_children(self, ...)
270 for k, node in ipairs(self.children) do
277 A simple template element
279 Template = class(Node)
281 function Template.__init__(self, template)
283 self.template = template
286 function Template.render(self)
287 luci.template.render(self.template, {self=self})
290 function Template.parse(self, readinput)
291 self.readinput = (readinput ~= false)
292 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
297 Map - A map describing a configuration file
301 function Map.__init__(self, config, ...)
302 Node.__init__(self, ...)
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)
325 function Map.formvalue(self, key)
326 return self.readinput and luci.http.formvalue(key)
329 function Map.formvaluetable(self, key)
330 return self.readinput and luci.http.formvaluetable(key) or {}
333 function Map.get_scheme(self, sectiontype, option)
335 return self.scheme and self.scheme.sections[sectiontype]
337 return self.scheme and self.scheme.variables[sectiontype]
338 and self.scheme.variables[sectiontype][option]
342 function Map.submitstate(self)
343 return self:formvalue("cbi.submit")
346 -- Chain foreign config
347 function Map.chain(self, config)
348 table.insert(self.parsechain, config)
351 function Map.state_handler(self, state)
355 -- Use optimized UCI writing
356 function Map.parse(self, readinput, ...)
357 self.readinput = (readinput ~= false)
358 self:_run_hooks("on_parse")
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 self:_run_hooks("on_before_commit")
373 for i, config in ipairs(self.parsechain) do
374 self.uci:commit(config)
376 -- Refresh data because commit changes section names
377 self.uci:load(config)
379 self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
380 if self.apply_on_parse then
381 self.uci:apply(self.parsechain)
382 self:_run_hooks("on_apply", "on_after_apply")
384 self._apply = function()
385 local cmd = self.uci:apply(self.parsechain, true)
391 Node.parse(self, true)
394 for i, config in ipairs(self.parsechain) do
395 self.uci:unload(config)
397 if type(self.commit_handler) == "function" then
398 self:commit_handler(self:submitstate())
402 if self:submitstate() then
403 if not self.save then
404 self.state = FORM_INVALID
405 elseif self.proceed then
406 self.state = FORM_PROCEED
408 self.state = self.changed and FORM_CHANGED or FORM_VALID
411 self.state = FORM_NODATA
414 return self:state_handler(self.state)
417 function Map.render(self, ...)
418 self:_run_hooks("on_init")
419 Node.render(self, ...)
421 local fp = self._apply()
424 self:_run_hooks("on_apply")
428 -- Creates a child section
429 function Map.section(self, class, ...)
430 if instanceof(class, AbstractSection) then
431 local obj = class(self, ...)
435 error("class must be a descendent of AbstractSection")
440 function Map.add(self, sectiontype)
441 return self.uci:add(self.config, sectiontype)
445 function Map.set(self, section, option, value)
447 return self.uci:set(self.config, section, option, value)
449 return self.uci:set(self.config, section, value)
454 function Map.del(self, section, option)
456 return self.uci:delete(self.config, section, option)
458 return self.uci:delete(self.config, section)
463 function Map.get(self, section, option)
465 return self.uci:get_all(self.config)
467 return self.uci:get(self.config, section, option)
469 return self.uci:get_all(self.config, section)
476 Compound = class(Node)
478 function Compound.__init__(self, ...)
480 self.template = "cbi/compound"
481 self.children = {...}
484 function Compound.populate_delegator(self, delegator)
485 for _, v in ipairs(self.children) do
486 v.delegator = delegator
490 function Compound.parse(self, ...)
491 local cstate, state = 0
493 for k, child in ipairs(self.children) do
494 cstate = child:parse(...)
495 state = (not state or cstate < state) and cstate or state
503 Delegator - Node controller
505 Delegator = class(Node)
506 function Delegator.__init__(self, ...)
507 Node.__init__(self, ...)
509 self.defaultpath = {}
510 self.pageaction = false
511 self.readinput = true
512 self.allow_reset = false
513 self.allow_cancel = false
514 self.allow_back = false
515 self.allow_finish = false
516 self.template = "cbi/delegator"
519 function Delegator.set(self, name, node)
520 assert(not self.nodes[name], "Duplicate entry")
522 self.nodes[name] = node
525 function Delegator.add(self, name, node)
526 node = self:set(name, node)
527 self.defaultpath[#self.defaultpath+1] = name
530 function Delegator.insert_after(self, name, after)
531 local n = #self.chain + 1
532 for k, v in ipairs(self.chain) do
538 table.insert(self.chain, n, name)
541 function Delegator.set_route(self, ...)
542 local n, chain, route = 0, self.chain, {...}
544 if chain[i] == self.current then
553 for i = n + 1, #chain do
558 function Delegator.get(self, name)
559 local node = self.nodes[name]
561 if type(node) == "string" then
562 node = load(node, name)
565 if type(node) == "table" and getmetatable(node) == nil then
566 node = Compound(unpack(node))
572 function Delegator.parse(self, ...)
573 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
574 if self:_run_hooks("on_cancel") then
579 if not Map.formvalue(self, "cbi.delg.current") then
580 self:_run_hooks("on_init")
584 self.chain = self.chain or self:get_chain()
585 self.current = self.current or self:get_active()
586 self.active = self.active or self:get(self.current)
587 assert(self.active, "Invalid state")
589 local stat = FORM_DONE
590 if type(self.active) ~= "function" then
591 self.active:populate_delegator(self)
592 stat = self.active:parse()
597 if stat > FORM_PROCEED then
598 if Map.formvalue(self, "cbi.delg.back") then
599 newcurrent = self:get_prev(self.current)
601 newcurrent = self:get_next(self.current)
603 elseif stat < FORM_PROCEED then
608 if not Map.formvalue(self, "cbi.submit") then
610 elseif stat > FORM_PROCEED
611 and (not newcurrent or not self:get(newcurrent)) then
612 return self:_run_hook("on_done") or FORM_DONE
614 self.current = newcurrent or self.current
615 self.active = self:get(self.current)
616 if type(self.active) ~= "function" then
617 self.active:populate_delegator(self)
618 local stat = self.active:parse(false)
619 if stat == FORM_SKIP then
620 return self:parse(...)
625 return self:parse(...)
630 function Delegator.get_next(self, state)
631 for k, v in ipairs(self.chain) do
633 return self.chain[k+1]
638 function Delegator.get_prev(self, state)
639 for k, v in ipairs(self.chain) do
641 return self.chain[k-1]
646 function Delegator.get_chain(self)
647 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
648 return type(x) == "table" and x or {x}
651 function Delegator.get_active(self)
652 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
660 Page.__init__ = Node.__init__
661 Page.parse = function() end
665 SimpleForm - A Simple non-UCI form
667 SimpleForm = class(Node)
669 function SimpleForm.__init__(self, config, title, description, data)
670 Node.__init__(self, title, description)
672 self.data = data or {}
673 self.template = "cbi/simpleform"
675 self.pageaction = false
676 self.readinput = true
679 SimpleForm.formvalue = Map.formvalue
680 SimpleForm.formvaluetable = Map.formvaluetable
682 function SimpleForm.parse(self, readinput, ...)
683 self.readinput = (readinput ~= false)
685 if self:formvalue("cbi.skip") then
689 if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
693 if self:submitstate() then
694 Node.parse(self, 1, ...)
698 for k, j in ipairs(self.children) do
699 for i, v in ipairs(j.children) do
701 and (not v.tag_missing or not v.tag_missing[1])
702 and (not v.tag_invalid or not v.tag_invalid[1])
708 not self:submitstate() and FORM_NODATA
709 or valid and FORM_VALID
712 self.dorender = not self.handle
714 local nrender, nstate = self:handle(state, self.data)
715 self.dorender = self.dorender or (nrender ~= false)
716 state = nstate or state
721 function SimpleForm.render(self, ...)
722 if self.dorender then
723 Node.render(self, ...)
727 function SimpleForm.submitstate(self)
728 return self:formvalue("cbi.submit")
731 function SimpleForm.section(self, class, ...)
732 if instanceof(class, AbstractSection) then
733 local obj = class(self, ...)
737 error("class must be a descendent of AbstractSection")
741 -- Creates a child field
742 function SimpleForm.field(self, class, ...)
744 for k, v in ipairs(self.children) do
745 if instanceof(v, SimpleSection) then
751 section = self:section(SimpleSection)
754 if instanceof(class, AbstractValue) then
755 local obj = class(self, section, ...)
756 obj.track_missing = true
760 error("class must be a descendent of AbstractValue")
764 function SimpleForm.set(self, section, option, value)
765 self.data[option] = value
769 function SimpleForm.del(self, section, option)
770 self.data[option] = nil
774 function SimpleForm.get(self, section, option)
775 return self.data[option]
779 function SimpleForm.get_scheme()
784 Form = class(SimpleForm)
786 function Form.__init__(self, ...)
787 SimpleForm.__init__(self, ...)
795 AbstractSection = class(Node)
797 function AbstractSection.__init__(self, map, sectiontype, ...)
798 Node.__init__(self, ...)
799 self.sectiontype = sectiontype
801 self.config = map.config
806 self.tag_invalid = {}
807 self.tag_deperror = {}
811 self.addremove = false
815 -- Define a tab for the section
816 function AbstractSection.tab(self, tab, title, desc)
817 self.tabs = self.tabs or { }
818 self.tab_names = self.tab_names or { }
820 self.tab_names[#self.tab_names+1] = tab
828 -- Appends a new option
829 function AbstractSection.option(self, class, option, ...)
830 -- Autodetect from UVL
831 if class == true and self.map:get_scheme(self.sectiontype, option) then
832 local vs = self.map:get_scheme(self.sectiontype, option)
833 if vs.type == "boolean" then
835 elseif vs.type == "list" then
837 elseif vs.type == "enum" or vs.type == "reference" then
844 if instanceof(class, AbstractValue) then
845 local obj = class(self.map, self, option, ...)
847 self.fields[option] = obj
849 elseif class == true then
850 error("No valid class was given and autodetection failed.")
852 error("class must be a descendant of AbstractValue")
856 -- Appends a new tabbed option
857 function AbstractSection.taboption(self, tab, ...)
859 assert(tab and self.tabs and self.tabs[tab],
860 "Cannot assign option to not existing tab %q" % tostring(tab))
862 local l = self.tabs[tab].childs
863 local o = AbstractSection.option(self, ...)
865 if o then l[#l+1] = o end
870 -- Render a single tab
871 function AbstractSection.render_tab(self, tab, ...)
873 assert(tab and self.tabs and self.tabs[tab],
874 "Cannot render not existing tab %q" % tostring(tab))
876 for _, node in ipairs(self.tabs[tab].childs) do
881 -- Parse optional options
882 function AbstractSection.parse_optionals(self, section)
883 if not self.optional then
887 self.optionals[section] = {}
889 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
890 for k,v in ipairs(self.children) do
891 if v.optional and not v:cfgvalue(section) then
892 if field == v.option then
894 self.map.proceed = true
896 table.insert(self.optionals[section], v)
901 if field and #field > 0 and self.dynamic then
902 self:add_dynamic(field)
906 -- Add a dynamic option
907 function AbstractSection.add_dynamic(self, field, optional)
908 local o = self:option(Value, field, field)
909 o.optional = optional
912 -- Parse all dynamic options
913 function AbstractSection.parse_dynamic(self, section)
914 if not self.dynamic then
918 local arr = luci.util.clone(self:cfgvalue(section))
919 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
920 for k, v in pairs(form) do
924 for key,val in pairs(arr) do
927 for i,c in ipairs(self.children) do
928 if c.option == key then
933 if create and key:sub(1, 1) ~= "." then
934 self.map.proceed = true
935 self:add_dynamic(key, true)
940 -- Returns the section's UCI table
941 function AbstractSection.cfgvalue(self, section)
942 return self.map:get(section)
946 function AbstractSection.push_events(self)
947 --luci.util.append(self.map.events, self.events)
948 self.map.changed = true
951 -- Removes the section
952 function AbstractSection.remove(self, section)
953 self.map.proceed = true
954 return self.map:del(section)
957 -- Creates the section
958 function AbstractSection.create(self, section)
962 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
964 section = self.map:add(self.sectiontype)
969 for k,v in pairs(self.children) do
971 self.map:set(section, v.option, v.default)
975 for k,v in pairs(self.defaults) do
976 self.map:set(section, k, v)
980 self.map.proceed = true
986 SimpleSection = class(AbstractSection)
988 function SimpleSection.__init__(self, form, ...)
989 AbstractSection.__init__(self, form, nil, ...)
990 self.template = "cbi/nullsection"
994 Table = class(AbstractSection)
996 function Table.__init__(self, form, data, ...)
997 local datasource = {}
999 datasource.config = "table"
1000 self.data = data or {}
1002 datasource.formvalue = Map.formvalue
1003 datasource.formvaluetable = Map.formvaluetable
1004 datasource.readinput = true
1006 function datasource.get(self, section, option)
1007 return tself.data[section] and tself.data[section][option]
1010 function datasource.submitstate(self)
1011 return Map.formvalue(self, "cbi.submit")
1014 function datasource.del(...)
1018 function datasource.get_scheme()
1022 AbstractSection.__init__(self, datasource, "table", ...)
1023 self.template = "cbi/tblsection"
1024 self.rowcolors = true
1025 self.anonymous = true
1028 function Table.parse(self, readinput)
1029 self.map.readinput = (readinput ~= false)
1030 for i, k in ipairs(self:cfgsections()) do
1031 if self.map:submitstate() then
1037 function Table.cfgsections(self)
1040 for i, v in luci.util.kspairs(self.data) do
1041 table.insert(sections, i)
1047 function Table.update(self, data)
1054 NamedSection - A fixed configuration section defined by its name
1056 NamedSection = class(AbstractSection)
1058 function NamedSection.__init__(self, map, section, stype, ...)
1059 AbstractSection.__init__(self, map, stype, ...)
1062 self.addremove = 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.title = self.title or vs.title
1070 self.description = self.description or vs.descr
1073 self.template = "cbi/nsection"
1074 self.section = section
1077 function NamedSection.parse(self, novld)
1078 local s = self.section
1079 local active = self:cfgvalue(s)
1081 if self.addremove then
1082 local path = self.config.."."..s
1083 if active then -- Remove the section
1084 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1088 else -- Create and apply default values
1089 if self.map:formvalue("cbi.cns."..path) then
1097 AbstractSection.parse_dynamic(self, s)
1098 if self.map:submitstate() then
1101 if not novld and not self.override_scheme and self.map.scheme then
1102 _uvl_validate_section(self, s)
1105 AbstractSection.parse_optionals(self, s)
1107 if self.changed then
1115 TypedSection - A (set of) configuration section(s) defined by the type
1116 addremove: Defines whether the user can add/remove sections of this type
1117 anonymous: Allow creating anonymous sections
1118 validate: a validation function returning nil if the section is invalid
1120 TypedSection = class(AbstractSection)
1122 function TypedSection.__init__(self, map, type, ...)
1123 AbstractSection.__init__(self, map, type, ...)
1125 self.template = "cbi/tsection"
1127 self.anonymous = false
1129 -- Use defaults from UVL
1130 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1131 local vs = self.map:get_scheme(self.sectiontype)
1132 self.addremove = not vs.unique and not vs.required
1133 self.dynamic = vs.dynamic
1134 self.anonymous = not vs.named
1135 self.title = self.title or vs.title
1136 self.description = self.description or vs.descr
1140 -- Return all matching UCI sections for this TypedSection
1141 function TypedSection.cfgsections(self)
1143 self.map.uci:foreach(self.map.config, self.sectiontype,
1145 if self:checkscope(section[".name"]) then
1146 table.insert(sections, section[".name"])
1153 -- Limits scope to sections that have certain option => value pairs
1154 function TypedSection.depends(self, option, value)
1155 table.insert(self.deps, {option=option, value=value})
1158 function TypedSection.parse(self, novld)
1159 if self.addremove then
1161 local crval = REMOVE_PREFIX .. self.config
1162 local name = self.map:formvaluetable(crval)
1163 for k,v in pairs(name) do
1164 if k:sub(-2) == ".x" then
1165 k = k:sub(1, #k - 2)
1167 if self:cfgvalue(k) and self:checkscope(k) then
1174 for i, k in ipairs(self:cfgsections()) do
1175 AbstractSection.parse_dynamic(self, k)
1176 if self.map:submitstate() then
1177 Node.parse(self, k, novld)
1179 if not novld and not self.override_scheme and self.map.scheme then
1180 _uvl_validate_section(self, k)
1183 AbstractSection.parse_optionals(self, k)
1186 if self.addremove then
1189 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1190 local name = self.map:formvalue(crval)
1191 if self.anonymous then
1193 created = self:create()
1197 -- Ignore if it already exists
1198 if self:cfgvalue(name) then
1202 name = self:checkscope(name)
1205 self.err_invalid = true
1208 if name and #name > 0 then
1209 created = self:create(name) and name
1211 self.invalid_cts = true
1218 AbstractSection.parse_optionals(self, created)
1222 if created or self.changed then
1227 -- Verifies scope of sections
1228 function TypedSection.checkscope(self, section)
1229 -- Check if we are not excluded
1230 if self.filter and not self:filter(section) then
1234 -- Check if at least one dependency is met
1235 if #self.deps > 0 and self:cfgvalue(section) then
1238 for k, v in ipairs(self.deps) do
1239 if self:cfgvalue(section)[v.option] == v.value then
1249 return self:validate(section)
1253 -- Dummy validate function
1254 function TypedSection.validate(self, section)
1260 AbstractValue - An abstract Value Type
1261 null: Value can be empty
1262 valid: A function returning the value if it is valid otherwise nil
1263 depends: A table of option => value pairs of which one must be true
1264 default: The default value
1265 size: The size of the input fields
1266 rmempty: Unset value if empty
1267 optional: This value is optional (see AbstractSection.optionals)
1269 AbstractValue = class(Node)
1271 function AbstractValue.__init__(self, map, section, option, ...)
1272 Node.__init__(self, ...)
1273 self.section = section
1274 self.option = option
1276 self.config = map.config
1277 self.tag_invalid = {}
1278 self.tag_missing = {}
1279 self.tag_reqerror = {}
1283 --self.cast = "string"
1285 self.track_missing = false
1289 self.optional = false
1292 function AbstractValue.prepare(self)
1293 -- Use defaults from UVL
1294 if not self.override_scheme
1295 and self.map:get_scheme(self.section.sectiontype, self.option) then
1296 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1297 if self.cast == nil then
1298 self.cast = (vs.type == "list") and "list" or "string"
1300 self.title = self.title or vs.title
1301 self.description = self.description or vs.descr
1302 if self.default == nil then
1303 self.default = vs.default
1306 if vs.depends and not self.override_dependencies then
1307 for i, deps in ipairs(vs.depends) do
1308 deps = _uvl_strip_remote_dependencies(deps)
1316 self.cast = self.cast or "string"
1319 -- Add a dependencie to another section field
1320 function AbstractValue.depends(self, field, value)
1322 if type(field) == "string" then
1329 table.insert(self.deps, {deps=deps, add=""})
1332 -- Generates the unique CBID
1333 function AbstractValue.cbid(self, section)
1334 return "cbid."..self.map.config.."."..section.."."..self.option
1337 -- Return whether this object should be created
1338 function AbstractValue.formcreated(self, section)
1339 local key = "cbi.opt."..self.config.."."..section
1340 return (self.map:formvalue(key) == self.option)
1343 -- Returns the formvalue for this object
1344 function AbstractValue.formvalue(self, section)
1345 return self.map:formvalue(self:cbid(section))
1348 function AbstractValue.additional(self, value)
1349 self.optional = value
1352 function AbstractValue.mandatory(self, value)
1353 self.rmempty = not value
1356 function AbstractValue.parse(self, section, novld)
1357 local fvalue = self:formvalue(section)
1358 local cvalue = self:cfgvalue(section)
1360 -- If favlue and cvalue are both tables and have the same content
1361 -- make them identical
1362 if type(fvalue) == "table" and type(cvalue) == "table" then
1363 local equal = #fvalue == #cvalue
1366 if cvalue[i] ~= fvalue[i] then
1376 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1377 fvalue = self:transform(self:validate(fvalue, section))
1378 if not fvalue and not novld then
1380 self.error[section] = "invalid"
1382 self.error = { [section] = "invalid" }
1384 if self.section.error then
1385 table.insert(self.section.error[section], "invalid")
1387 self.section.error = {[section] = {"invalid"}}
1389 self.map.save = false
1391 if fvalue and not (fvalue == cvalue) then
1392 if self:write(section, fvalue) then
1394 self.section.changed = true
1395 --luci.util.append(self.map.events, self.events)
1398 else -- Unset the UCI or error
1399 if self.rmempty or self.optional then
1400 if self:remove(section) then
1402 self.section.changed = true
1403 --luci.util.append(self.map.events, self.events)
1405 elseif cvalue ~= fvalue and not novld then
1406 self:write(section, fvalue or "")
1408 self.error[section] = "missing"
1410 self.error = { [section] = "missing" }
1412 self.map.save = false
1417 -- Render if this value exists or if it is mandatory
1418 function AbstractValue.render(self, s, scope)
1419 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1422 scope.cbid = self:cbid(s)
1423 scope.striptags = luci.util.striptags
1424 scope.pcdata = luci.util.pcdata
1426 scope.ifattr = function(cond,key,val)
1428 return string.format(
1429 ' %s="%s"', tostring(key),
1430 luci.util.pcdata(tostring( val
1432 or (type(self[key]) ~= "function" and self[key])
1440 scope.attr = function(...)
1441 return scope.ifattr( true, ... )
1444 Node.render(self, scope)
1448 -- Return the UCI value of this object
1449 function AbstractValue.cfgvalue(self, section)
1450 local value = self.map:get(section, self.option)
1453 elseif not self.cast or self.cast == type(value) then
1455 elseif self.cast == "string" then
1456 if type(value) == "table" then
1459 elseif self.cast == "table" then
1460 return luci.util.split(value, "%s+", nil, true)
1464 -- Validate the form value
1465 function AbstractValue.validate(self, value)
1469 AbstractValue.transform = AbstractValue.validate
1473 function AbstractValue.write(self, section, value)
1474 return self.map:set(section, self.option, value)
1478 function AbstractValue.remove(self, section)
1479 return self.map:del(section, self.option)
1486 Value - A one-line value
1487 maxlength: The maximum length
1489 Value = class(AbstractValue)
1491 function Value.__init__(self, ...)
1492 AbstractValue.__init__(self, ...)
1493 self.template = "cbi/value"
1498 function Value.value(self, key, val)
1500 table.insert(self.keylist, tostring(key))
1501 table.insert(self.vallist, tostring(val))
1505 -- DummyValue - This does nothing except being there
1506 DummyValue = class(AbstractValue)
1508 function DummyValue.__init__(self, ...)
1509 AbstractValue.__init__(self, ...)
1510 self.template = "cbi/dvalue"
1514 function DummyValue.cfgvalue(self, section)
1517 if type(self.value) == "function" then
1518 value = self:value(section)
1523 value = AbstractValue.cfgvalue(self, section)
1528 function DummyValue.parse(self)
1534 Flag - A flag being enabled or disabled
1536 Flag = class(AbstractValue)
1538 function Flag.__init__(self, ...)
1539 AbstractValue.__init__(self, ...)
1540 self.template = "cbi/fvalue"
1546 -- A flag can only have two states: set or unset
1547 function Flag.parse(self, section)
1548 local fvalue = self:formvalue(section)
1551 fvalue = self.enabled
1553 fvalue = self.disabled
1556 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1557 if not(fvalue == self:cfgvalue(section)) then
1558 self:write(section, fvalue)
1561 self:remove(section)
1568 ListValue - A one-line value predefined in a list
1569 widget: The widget that will be used (select, radio)
1571 ListValue = class(AbstractValue)
1573 function ListValue.__init__(self, ...)
1574 AbstractValue.__init__(self, ...)
1575 self.template = "cbi/lvalue"
1580 self.widget = "select"
1583 function ListValue.prepare(self, ...)
1584 AbstractValue.prepare(self, ...)
1585 if not self.override_scheme
1586 and self.map:get_scheme(self.section.sectiontype, self.option) then
1587 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1588 if self.value and vs.valuelist and not self.override_values then
1589 for k, v in ipairs(vs.valuelist) do
1591 if not self.override_dependencies
1592 and vs.enum_depends and vs.enum_depends[v.value] then
1593 for i, dep in ipairs(vs.enum_depends[v.value]) do
1594 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1597 self:value(v.value, v.title or v.value, unpack(deps))
1603 function ListValue.value(self, key, val, ...)
1604 if luci.util.contains(self.keylist, key) then
1609 table.insert(self.keylist, tostring(key))
1610 table.insert(self.vallist, tostring(val))
1612 for i, deps in ipairs({...}) do
1613 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1617 function ListValue.validate(self, val)
1618 if luci.util.contains(self.keylist, val) then
1628 MultiValue - Multiple delimited values
1629 widget: The widget that will be used (select, checkbox)
1630 delimiter: The delimiter that will separate the values (default: " ")
1632 MultiValue = class(AbstractValue)
1634 function MultiValue.__init__(self, ...)
1635 AbstractValue.__init__(self, ...)
1636 self.template = "cbi/mvalue"
1641 self.widget = "checkbox"
1642 self.delimiter = " "
1645 function MultiValue.render(self, ...)
1646 if self.widget == "select" and not self.size then
1647 self.size = #self.vallist
1650 AbstractValue.render(self, ...)
1653 function MultiValue.value(self, key, val)
1654 if luci.util.contains(self.keylist, key) then
1659 table.insert(self.keylist, tostring(key))
1660 table.insert(self.vallist, tostring(val))
1663 function MultiValue.valuelist(self, section)
1664 local val = self:cfgvalue(section)
1666 if not(type(val) == "string") then
1670 return luci.util.split(val, self.delimiter)
1673 function MultiValue.validate(self, val)
1674 val = (type(val) == "table") and val or {val}
1678 for i, value in ipairs(val) do
1679 if luci.util.contains(self.keylist, value) then
1680 result = result and (result .. self.delimiter .. value) or value
1688 StaticList = class(MultiValue)
1690 function StaticList.__init__(self, ...)
1691 MultiValue.__init__(self, ...)
1693 self.valuelist = self.cfgvalue
1695 if not self.override_scheme
1696 and self.map:get_scheme(self.section.sectiontype, self.option) then
1697 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1698 if self.value and vs.values and not self.override_values then
1699 for k, v in pairs(vs.values) do
1706 function StaticList.validate(self, value)
1707 value = (type(value) == "table") and value or {value}
1710 for i, v in ipairs(value) do
1711 if luci.util.contains(self.keylist, v) then
1712 table.insert(valid, v)
1719 DynamicList = class(AbstractValue)
1721 function DynamicList.__init__(self, ...)
1722 AbstractValue.__init__(self, ...)
1723 self.template = "cbi/dynlist"
1729 function DynamicList.value(self, key, val)
1731 table.insert(self.keylist, tostring(key))
1732 table.insert(self.vallist, tostring(val))
1735 function DynamicList.write(self, ...)
1736 self.map.proceed = true
1737 return AbstractValue.write(self, ...)
1740 function DynamicList.formvalue(self, section)
1741 local value = AbstractValue.formvalue(self, section)
1742 value = (type(value) == "table") and value or {value}
1745 for i, v in ipairs(value) do
1747 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1748 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1749 table.insert(valid, v)
1758 TextValue - A multi-line value
1761 TextValue = class(AbstractValue)
1763 function TextValue.__init__(self, ...)
1764 AbstractValue.__init__(self, ...)
1765 self.template = "cbi/tvalue"
1771 Button = class(AbstractValue)
1773 function Button.__init__(self, ...)
1774 AbstractValue.__init__(self, ...)
1775 self.template = "cbi/button"
1776 self.inputstyle = nil
1781 FileUpload = class(AbstractValue)
1783 function FileUpload.__init__(self, ...)
1784 AbstractValue.__init__(self, ...)
1785 self.template = "cbi/upload"
1786 if not self.map.upload_fields then
1787 self.map.upload_fields = { self }
1789 self.map.upload_fields[#self.map.upload_fields+1] = self
1793 function FileUpload.formcreated(self, section)
1794 return AbstractValue.formcreated(self, section) or
1795 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1796 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1799 function FileUpload.cfgvalue(self, section)
1800 local val = AbstractValue.cfgvalue(self, section)
1801 if val and fs.access(val) then
1807 function FileUpload.formvalue(self, section)
1808 local val = AbstractValue.formvalue(self, section)
1810 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1811 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1821 function FileUpload.remove(self, section)
1822 local val = AbstractValue.formvalue(self, section)
1823 if val and fs.access(val) then fs.unlink(val) end
1824 return AbstractValue.remove(self, section)
1828 FileBrowser = class(AbstractValue)
1830 function FileBrowser.__init__(self, ...)
1831 AbstractValue.__init__(self, ...)
1832 self.template = "cbi/browser"