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(cbimap) then
66 func, err = loadfile(cbimap)
67 elseif fs.access(cbidir..cbimap..".lua") then
68 func, err = loadfile(cbidir..cbimap..".lua")
69 elseif fs.access(cbidir..cbimap..".lua.gz") then
70 func, err = loadfile(cbidir..cbimap..".lua.gz")
72 func, err = nil, "Model '" .. cbimap .. "' not found!"
77 luci.i18n.loadc("base")
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._run_hook(self, hook)
226 if type(self[hook]) == "function" then
227 return self[hook](self)
231 function Node._run_hooks(self, ...)
234 for _, f in ipairs(arg) do
235 if type(self[f]) == "function" then
244 function Node.prepare(self, ...)
245 for k, child in ipairs(self.children) do
250 -- Append child nodes
251 function Node.append(self, obj)
252 table.insert(self.children, obj)
255 -- Parse this node and its children
256 function Node.parse(self, ...)
257 for k, child in ipairs(self.children) do
263 function Node.render(self, scope)
267 luci.template.render(self.template, scope)
270 -- Render the children
271 function Node.render_children(self, ...)
272 for k, node in ipairs(self.children) do
279 A simple template element
281 Template = class(Node)
283 function Template.__init__(self, template)
285 self.template = template
288 function Template.render(self)
289 luci.template.render(self.template, {self=self})
292 function Template.parse(self, readinput)
293 self.readinput = (readinput ~= false)
294 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
299 Map - A map describing a configuration file
303 function Map.__init__(self, config, ...)
304 Node.__init__(self, ...)
307 self.parsechain = {self.config}
308 self.template = "cbi/map"
309 self.apply_on_parse = nil
310 self.readinput = true
314 self.uci = uci.cursor()
319 if not self.uci:load(self.config) then
320 error("Unable to read UCI data: " .. self.config)
323 self.validator = luci.uvl.UVL()
324 self.scheme = self.validator:get_scheme(self.config)
327 function Map.formvalue(self, key)
328 return self.readinput and luci.http.formvalue(key)
331 function Map.formvaluetable(self, key)
332 return self.readinput and luci.http.formvaluetable(key) or {}
335 function Map.get_scheme(self, sectiontype, option)
337 return self.scheme and self.scheme.sections[sectiontype]
339 return self.scheme and self.scheme.variables[sectiontype]
340 and self.scheme.variables[sectiontype][option]
344 function Map.submitstate(self)
345 return self:formvalue("cbi.submit")
348 -- Chain foreign config
349 function Map.chain(self, config)
350 table.insert(self.parsechain, config)
353 function Map.state_handler(self, state)
357 -- Use optimized UCI writing
358 function Map.parse(self, readinput, ...)
359 self.readinput = (readinput ~= false)
360 self:_run_hooks("on_parse")
362 if self:formvalue("cbi.skip") then
363 self.state = FORM_SKIP
364 return self:state_handler(self.state)
367 Node.parse(self, ...)
370 for i, config in ipairs(self.parsechain) do
371 self.uci:save(config)
373 if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
374 self:_run_hooks("on_before_commit")
375 for i, config in ipairs(self.parsechain) do
376 self.uci:commit(config)
378 -- Refresh data because commit changes section names
379 self.uci:load(config)
381 self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
382 if self.apply_on_parse then
383 self.uci:apply(self.parsechain)
384 self:_run_hooks("on_apply", "on_after_apply")
386 self._apply = function()
387 local cmd = self.uci:apply(self.parsechain, true)
393 Node.parse(self, true)
396 for i, config in ipairs(self.parsechain) do
397 self.uci:unload(config)
399 if type(self.commit_handler) == "function" then
400 self:commit_handler(self:submitstate())
404 if self:submitstate() then
405 if not self.save then
406 self.state = FORM_INVALID
407 elseif self.proceed then
408 self.state = FORM_PROCEED
410 self.state = self.changed and FORM_CHANGED or FORM_VALID
413 self.state = FORM_NODATA
416 return self:state_handler(self.state)
419 function Map.render(self, ...)
420 self:_run_hooks("on_init")
421 Node.render(self, ...)
423 local fp = self._apply()
426 self:_run_hooks("on_apply")
430 -- Creates a child section
431 function Map.section(self, class, ...)
432 if instanceof(class, AbstractSection) then
433 local obj = class(self, ...)
437 error("class must be a descendent of AbstractSection")
442 function Map.add(self, sectiontype)
443 return self.uci:add(self.config, sectiontype)
447 function Map.set(self, section, option, value)
449 return self.uci:set(self.config, section, option, value)
451 return self.uci:set(self.config, section, value)
456 function Map.del(self, section, option)
458 return self.uci:delete(self.config, section, option)
460 return self.uci:delete(self.config, section)
465 function Map.get(self, section, option)
467 return self.uci:get_all(self.config)
469 return self.uci:get(self.config, section, option)
471 return self.uci:get_all(self.config, section)
478 Compound = class(Node)
480 function Compound.__init__(self, ...)
482 self.template = "cbi/compound"
483 self.children = {...}
486 function Compound.populate_delegator(self, delegator)
487 for _, v in ipairs(self.children) do
488 v.delegator = delegator
492 function Compound.parse(self, ...)
493 local cstate, state = 0
495 for k, child in ipairs(self.children) do
496 cstate = child:parse(...)
497 state = (not state or cstate < state) and cstate or state
505 Delegator - Node controller
507 Delegator = class(Node)
508 function Delegator.__init__(self, ...)
509 Node.__init__(self, ...)
511 self.defaultpath = {}
512 self.pageaction = false
513 self.readinput = true
514 self.allow_reset = false
515 self.allow_cancel = false
516 self.allow_back = false
517 self.allow_finish = false
518 self.template = "cbi/delegator"
521 function Delegator.set(self, name, node)
522 assert(not self.nodes[name], "Duplicate entry")
524 self.nodes[name] = node
527 function Delegator.add(self, name, node)
528 node = self:set(name, node)
529 self.defaultpath[#self.defaultpath+1] = name
532 function Delegator.insert_after(self, name, after)
533 local n = #self.chain + 1
534 for k, v in ipairs(self.chain) do
540 table.insert(self.chain, n, name)
543 function Delegator.set_route(self, ...)
544 local n, chain, route = 0, self.chain, {...}
546 if chain[i] == self.current then
555 for i = n + 1, #chain do
560 function Delegator.get(self, name)
561 local node = self.nodes[name]
563 if type(node) == "string" then
567 if type(node) == "table" and getmetatable(node) == nil then
568 node = Compound(unpack(node))
574 function Delegator.parse(self, ...)
575 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
576 if self:_run_hooks("on_cancel") then
581 if not Map.formvalue(self, "cbi.delg.current") then
582 self:_run_hooks("on_init")
586 self.chain = self.chain or self:get_chain()
587 self.current = self.current or self:get_active()
588 self.active = self.active or self:get(self.current)
589 assert(self.active, "Invalid state")
591 local stat = FORM_DONE
592 if type(self.active) ~= "function" then
593 self.active:populate_delegator(self)
594 stat = self.active:parse()
599 if stat > FORM_PROCEED then
600 if Map.formvalue(self, "cbi.delg.back") then
601 newcurrent = self:get_prev(self.current)
603 newcurrent = self:get_next(self.current)
605 elseif stat < FORM_PROCEED then
610 if not Map.formvalue(self, "cbi.submit") then
612 elseif stat > FORM_PROCEED
613 and (not newcurrent or not self:get(newcurrent)) then
614 return self:_run_hook("on_done") or FORM_DONE
616 self.current = newcurrent or self.current
617 self.active = self:get(self.current)
618 if type(self.active) ~= "function" then
619 self.active:populate_delegator(self)
620 local stat = self.active:parse(false)
621 if stat == FORM_SKIP then
622 return self:parse(...)
627 return self:parse(...)
632 function Delegator.get_next(self, state)
633 for k, v in ipairs(self.chain) do
635 return self.chain[k+1]
640 function Delegator.get_prev(self, state)
641 for k, v in ipairs(self.chain) do
643 return self.chain[k-1]
648 function Delegator.get_chain(self)
649 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
650 return type(x) == "table" and x or {x}
653 function Delegator.get_active(self)
654 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
662 Page.__init__ = Node.__init__
663 Page.parse = function() end
667 SimpleForm - A Simple non-UCI form
669 SimpleForm = class(Node)
671 function SimpleForm.__init__(self, config, title, description, data)
672 Node.__init__(self, title, description)
674 self.data = data or {}
675 self.template = "cbi/simpleform"
677 self.pageaction = false
678 self.readinput = true
681 SimpleForm.formvalue = Map.formvalue
682 SimpleForm.formvaluetable = Map.formvaluetable
684 function SimpleForm.parse(self, readinput, ...)
685 self.readinput = (readinput ~= false)
687 if self:formvalue("cbi.skip") then
691 if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
695 if self:submitstate() then
696 Node.parse(self, 1, ...)
700 for k, j in ipairs(self.children) do
701 for i, v in ipairs(j.children) do
703 and (not v.tag_missing or not v.tag_missing[1])
704 and (not v.tag_invalid or not v.tag_invalid[1])
710 not self:submitstate() and FORM_NODATA
711 or valid and FORM_VALID
714 self.dorender = not self.handle
716 local nrender, nstate = self:handle(state, self.data)
717 self.dorender = self.dorender or (nrender ~= false)
718 state = nstate or state
723 function SimpleForm.render(self, ...)
724 if self.dorender then
725 Node.render(self, ...)
729 function SimpleForm.submitstate(self)
730 return self:formvalue("cbi.submit")
733 function SimpleForm.section(self, class, ...)
734 if instanceof(class, AbstractSection) then
735 local obj = class(self, ...)
739 error("class must be a descendent of AbstractSection")
743 -- Creates a child field
744 function SimpleForm.field(self, class, ...)
746 for k, v in ipairs(self.children) do
747 if instanceof(v, SimpleSection) then
753 section = self:section(SimpleSection)
756 if instanceof(class, AbstractValue) then
757 local obj = class(self, section, ...)
758 obj.track_missing = true
762 error("class must be a descendent of AbstractValue")
766 function SimpleForm.set(self, section, option, value)
767 self.data[option] = value
771 function SimpleForm.del(self, section, option)
772 self.data[option] = nil
776 function SimpleForm.get(self, section, option)
777 return self.data[option]
781 function SimpleForm.get_scheme()
786 Form = class(SimpleForm)
788 function Form.__init__(self, ...)
789 SimpleForm.__init__(self, ...)
797 AbstractSection = class(Node)
799 function AbstractSection.__init__(self, map, sectiontype, ...)
800 Node.__init__(self, ...)
801 self.sectiontype = sectiontype
803 self.config = map.config
808 self.tag_invalid = {}
809 self.tag_deperror = {}
813 self.addremove = false
817 -- Define a tab for the section
818 function AbstractSection.tab(self, tab, title, desc)
819 self.tabs = self.tabs or { }
820 self.tab_names = self.tab_names or { }
822 self.tab_names[#self.tab_names+1] = tab
830 -- Appends a new option
831 function AbstractSection.option(self, class, option, ...)
832 -- Autodetect from UVL
833 if class == true and self.map:get_scheme(self.sectiontype, option) then
834 local vs = self.map:get_scheme(self.sectiontype, option)
835 if vs.type == "boolean" then
837 elseif vs.type == "list" then
839 elseif vs.type == "enum" or vs.type == "reference" then
846 if instanceof(class, AbstractValue) then
847 local obj = class(self.map, self, option, ...)
849 self.fields[option] = obj
851 elseif class == true then
852 error("No valid class was given and autodetection failed.")
854 error("class must be a descendant of AbstractValue")
858 -- Appends a new tabbed option
859 function AbstractSection.taboption(self, tab, ...)
861 assert(tab and self.tabs and self.tabs[tab],
862 "Cannot assign option to not existing tab %q" % tostring(tab))
864 local l = self.tabs[tab].childs
865 local o = AbstractSection.option(self, ...)
867 if o then l[#l+1] = o end
872 -- Render a single tab
873 function AbstractSection.render_tab(self, tab, ...)
875 assert(tab and self.tabs and self.tabs[tab],
876 "Cannot render not existing tab %q" % tostring(tab))
878 for _, node in ipairs(self.tabs[tab].childs) do
883 -- Parse optional options
884 function AbstractSection.parse_optionals(self, section)
885 if not self.optional then
889 self.optionals[section] = {}
891 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
892 for k,v in ipairs(self.children) do
893 if v.optional and not v:cfgvalue(section) then
894 if field == v.option then
896 self.map.proceed = true
898 table.insert(self.optionals[section], v)
903 if field and #field > 0 and self.dynamic then
904 self:add_dynamic(field)
908 -- Add a dynamic option
909 function AbstractSection.add_dynamic(self, field, optional)
910 local o = self:option(Value, field, field)
911 o.optional = optional
914 -- Parse all dynamic options
915 function AbstractSection.parse_dynamic(self, section)
916 if not self.dynamic then
920 local arr = luci.util.clone(self:cfgvalue(section))
921 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
922 for k, v in pairs(form) do
926 for key,val in pairs(arr) do
929 for i,c in ipairs(self.children) do
930 if c.option == key then
935 if create and key:sub(1, 1) ~= "." then
936 self.map.proceed = true
937 self:add_dynamic(key, true)
942 -- Returns the section's UCI table
943 function AbstractSection.cfgvalue(self, section)
944 return self.map:get(section)
948 function AbstractSection.push_events(self)
949 --luci.util.append(self.map.events, self.events)
950 self.map.changed = true
953 -- Removes the section
954 function AbstractSection.remove(self, section)
955 self.map.proceed = true
956 return self.map:del(section)
959 -- Creates the section
960 function AbstractSection.create(self, section)
964 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
966 section = self.map:add(self.sectiontype)
971 for k,v in pairs(self.children) do
973 self.map:set(section, v.option, v.default)
977 for k,v in pairs(self.defaults) do
978 self.map:set(section, k, v)
982 self.map.proceed = true
988 SimpleSection = class(AbstractSection)
990 function SimpleSection.__init__(self, form, ...)
991 AbstractSection.__init__(self, form, nil, ...)
992 self.template = "cbi/nullsection"
996 Table = class(AbstractSection)
998 function Table.__init__(self, form, data, ...)
999 local datasource = {}
1001 datasource.config = "table"
1002 self.data = data or {}
1004 datasource.formvalue = Map.formvalue
1005 datasource.formvaluetable = Map.formvaluetable
1006 datasource.readinput = true
1008 function datasource.get(self, section, option)
1009 return tself.data[section] and tself.data[section][option]
1012 function datasource.submitstate(self)
1013 return Map.formvalue(self, "cbi.submit")
1016 function datasource.del(...)
1020 function datasource.get_scheme()
1024 AbstractSection.__init__(self, datasource, "table", ...)
1025 self.template = "cbi/tblsection"
1026 self.rowcolors = true
1027 self.anonymous = true
1030 function Table.parse(self, readinput)
1031 self.map.readinput = (readinput ~= false)
1032 for i, k in ipairs(self:cfgsections()) do
1033 if self.map:submitstate() then
1039 function Table.cfgsections(self)
1042 for i, v in luci.util.kspairs(self.data) do
1043 table.insert(sections, i)
1049 function Table.update(self, data)
1056 NamedSection - A fixed configuration section defined by its name
1058 NamedSection = class(AbstractSection)
1060 function NamedSection.__init__(self, map, section, stype, ...)
1061 AbstractSection.__init__(self, map, stype, ...)
1064 self.addremove = false
1066 -- Use defaults from UVL
1067 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1068 local vs = self.map:get_scheme(self.sectiontype)
1069 self.addremove = not vs.unique and not vs.required
1070 self.dynamic = vs.dynamic
1071 self.title = self.title or vs.title
1072 self.description = self.description or vs.descr
1075 self.template = "cbi/nsection"
1076 self.section = section
1079 function NamedSection.parse(self, novld)
1080 local s = self.section
1081 local active = self:cfgvalue(s)
1083 if self.addremove then
1084 local path = self.config.."."..s
1085 if active then -- Remove the section
1086 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1090 else -- Create and apply default values
1091 if self.map:formvalue("cbi.cns."..path) then
1099 AbstractSection.parse_dynamic(self, s)
1100 if self.map:submitstate() then
1103 if not novld and not self.override_scheme and self.map.scheme then
1104 _uvl_validate_section(self, s)
1107 AbstractSection.parse_optionals(self, s)
1109 if self.changed then
1117 TypedSection - A (set of) configuration section(s) defined by the type
1118 addremove: Defines whether the user can add/remove sections of this type
1119 anonymous: Allow creating anonymous sections
1120 validate: a validation function returning nil if the section is invalid
1122 TypedSection = class(AbstractSection)
1124 function TypedSection.__init__(self, map, type, ...)
1125 AbstractSection.__init__(self, map, type, ...)
1127 self.template = "cbi/tsection"
1129 self.anonymous = false
1131 -- Use defaults from UVL
1132 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1133 local vs = self.map:get_scheme(self.sectiontype)
1134 self.addremove = not vs.unique and not vs.required
1135 self.dynamic = vs.dynamic
1136 self.anonymous = not vs.named
1137 self.title = self.title or vs.title
1138 self.description = self.description or vs.descr
1142 -- Return all matching UCI sections for this TypedSection
1143 function TypedSection.cfgsections(self)
1145 self.map.uci:foreach(self.map.config, self.sectiontype,
1147 if self:checkscope(section[".name"]) then
1148 table.insert(sections, section[".name"])
1155 -- Limits scope to sections that have certain option => value pairs
1156 function TypedSection.depends(self, option, value)
1157 table.insert(self.deps, {option=option, value=value})
1160 function TypedSection.parse(self, novld)
1161 if self.addremove then
1163 local crval = REMOVE_PREFIX .. self.config
1164 local name = self.map:formvaluetable(crval)
1165 for k,v in pairs(name) do
1166 if k:sub(-2) == ".x" then
1167 k = k:sub(1, #k - 2)
1169 if self:cfgvalue(k) and self:checkscope(k) then
1176 for i, k in ipairs(self:cfgsections()) do
1177 AbstractSection.parse_dynamic(self, k)
1178 if self.map:submitstate() then
1179 Node.parse(self, k, novld)
1181 if not novld and not self.override_scheme and self.map.scheme then
1182 _uvl_validate_section(self, k)
1185 AbstractSection.parse_optionals(self, k)
1188 if self.addremove then
1191 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1192 local name = self.map:formvalue(crval)
1193 if self.anonymous then
1195 created = self:create()
1199 -- Ignore if it already exists
1200 if self:cfgvalue(name) then
1204 name = self:checkscope(name)
1207 self.err_invalid = true
1210 if name and #name > 0 then
1211 created = self:create(name) and name
1213 self.invalid_cts = true
1220 AbstractSection.parse_optionals(self, created)
1224 if created or self.changed then
1229 -- Verifies scope of sections
1230 function TypedSection.checkscope(self, section)
1231 -- Check if we are not excluded
1232 if self.filter and not self:filter(section) then
1236 -- Check if at least one dependency is met
1237 if #self.deps > 0 and self:cfgvalue(section) then
1240 for k, v in ipairs(self.deps) do
1241 if self:cfgvalue(section)[v.option] == v.value then
1251 return self:validate(section)
1255 -- Dummy validate function
1256 function TypedSection.validate(self, section)
1262 AbstractValue - An abstract Value Type
1263 null: Value can be empty
1264 valid: A function returning the value if it is valid otherwise nil
1265 depends: A table of option => value pairs of which one must be true
1266 default: The default value
1267 size: The size of the input fields
1268 rmempty: Unset value if empty
1269 optional: This value is optional (see AbstractSection.optionals)
1271 AbstractValue = class(Node)
1273 function AbstractValue.__init__(self, map, section, option, ...)
1274 Node.__init__(self, ...)
1275 self.section = section
1276 self.option = option
1278 self.config = map.config
1279 self.tag_invalid = {}
1280 self.tag_missing = {}
1281 self.tag_reqerror = {}
1285 --self.cast = "string"
1287 self.track_missing = false
1291 self.optional = false
1294 function AbstractValue.prepare(self)
1295 -- Use defaults from UVL
1296 if not self.override_scheme
1297 and self.map:get_scheme(self.section.sectiontype, self.option) then
1298 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1299 if self.cast == nil then
1300 self.cast = (vs.type == "list") and "list" or "string"
1302 self.title = self.title or vs.title
1303 self.description = self.description or vs.descr
1304 if self.default == nil then
1305 self.default = vs.default
1308 if vs.depends and not self.override_dependencies then
1309 for i, deps in ipairs(vs.depends) do
1310 deps = _uvl_strip_remote_dependencies(deps)
1318 self.cast = self.cast or "string"
1321 -- Add a dependencie to another section field
1322 function AbstractValue.depends(self, field, value)
1324 if type(field) == "string" then
1331 table.insert(self.deps, {deps=deps, add=""})
1334 -- Generates the unique CBID
1335 function AbstractValue.cbid(self, section)
1336 return "cbid."..self.map.config.."."..section.."."..self.option
1339 -- Return whether this object should be created
1340 function AbstractValue.formcreated(self, section)
1341 local key = "cbi.opt."..self.config.."."..section
1342 return (self.map:formvalue(key) == self.option)
1345 -- Returns the formvalue for this object
1346 function AbstractValue.formvalue(self, section)
1347 return self.map:formvalue(self:cbid(section))
1350 function AbstractValue.additional(self, value)
1351 self.optional = value
1354 function AbstractValue.mandatory(self, value)
1355 self.rmempty = not value
1358 function AbstractValue.parse(self, section, novld)
1359 local fvalue = self:formvalue(section)
1360 local cvalue = self:cfgvalue(section)
1362 -- If favlue and cvalue are both tables and have the same content
1363 -- make them identical
1364 if type(fvalue) == "table" and type(cvalue) == "table" then
1365 local equal = #fvalue == #cvalue
1368 if cvalue[i] ~= fvalue[i] then
1378 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1379 fvalue = self:transform(self:validate(fvalue, section))
1380 if not fvalue and not novld then
1382 self.error[section] = "invalid"
1384 self.error = { [section] = "invalid" }
1386 if self.section.error then
1387 table.insert(self.section.error[section], "invalid")
1389 self.section.error = {[section] = {"invalid"}}
1391 self.map.save = false
1393 if fvalue and not (fvalue == cvalue) then
1394 if self:write(section, fvalue) then
1396 self.section.changed = true
1397 --luci.util.append(self.map.events, self.events)
1400 else -- Unset the UCI or error
1401 if self.rmempty or self.optional then
1402 if self:remove(section) then
1404 self.section.changed = true
1405 --luci.util.append(self.map.events, self.events)
1407 elseif cvalue ~= fvalue and not novld then
1408 self:write(section, fvalue or "")
1410 self.error[section] = "missing"
1412 self.error = { [section] = "missing" }
1414 self.map.save = false
1419 -- Render if this value exists or if it is mandatory
1420 function AbstractValue.render(self, s, scope)
1421 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1424 scope.cbid = self:cbid(s)
1425 scope.striptags = luci.util.striptags
1426 scope.pcdata = luci.util.pcdata
1428 scope.ifattr = function(cond,key,val)
1430 return string.format(
1431 ' %s="%s"', tostring(key),
1432 luci.util.pcdata(tostring( val
1434 or (type(self[key]) ~= "function" and self[key])
1442 scope.attr = function(...)
1443 return scope.ifattr( true, ... )
1446 Node.render(self, scope)
1450 -- Return the UCI value of this object
1451 function AbstractValue.cfgvalue(self, section)
1452 local value = self.map:get(section, self.option)
1455 elseif not self.cast or self.cast == type(value) then
1457 elseif self.cast == "string" then
1458 if type(value) == "table" then
1461 elseif self.cast == "table" then
1462 return luci.util.split(value, "%s+", nil, true)
1466 -- Validate the form value
1467 function AbstractValue.validate(self, value)
1471 AbstractValue.transform = AbstractValue.validate
1475 function AbstractValue.write(self, section, value)
1476 return self.map:set(section, self.option, value)
1480 function AbstractValue.remove(self, section)
1481 return self.map:del(section, self.option)
1488 Value - A one-line value
1489 maxlength: The maximum length
1491 Value = class(AbstractValue)
1493 function Value.__init__(self, ...)
1494 AbstractValue.__init__(self, ...)
1495 self.template = "cbi/value"
1500 function Value.value(self, key, val)
1502 table.insert(self.keylist, tostring(key))
1503 table.insert(self.vallist, tostring(val))
1507 -- DummyValue - This does nothing except being there
1508 DummyValue = class(AbstractValue)
1510 function DummyValue.__init__(self, ...)
1511 AbstractValue.__init__(self, ...)
1512 self.template = "cbi/dvalue"
1516 function DummyValue.cfgvalue(self, section)
1519 if type(self.value) == "function" then
1520 value = self:value(section)
1525 value = AbstractValue.cfgvalue(self, section)
1530 function DummyValue.parse(self)
1536 Flag - A flag being enabled or disabled
1538 Flag = class(AbstractValue)
1540 function Flag.__init__(self, ...)
1541 AbstractValue.__init__(self, ...)
1542 self.template = "cbi/fvalue"
1548 -- A flag can only have two states: set or unset
1549 function Flag.parse(self, section)
1550 local fvalue = self:formvalue(section)
1553 fvalue = self.enabled
1555 fvalue = self.disabled
1558 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1559 if not(fvalue == self:cfgvalue(section)) then
1560 self:write(section, fvalue)
1563 self:remove(section)
1570 ListValue - A one-line value predefined in a list
1571 widget: The widget that will be used (select, radio)
1573 ListValue = class(AbstractValue)
1575 function ListValue.__init__(self, ...)
1576 AbstractValue.__init__(self, ...)
1577 self.template = "cbi/lvalue"
1582 self.widget = "select"
1585 function ListValue.prepare(self, ...)
1586 AbstractValue.prepare(self, ...)
1587 if not self.override_scheme
1588 and self.map:get_scheme(self.section.sectiontype, self.option) then
1589 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1590 if self.value and vs.valuelist and not self.override_values then
1591 for k, v in ipairs(vs.valuelist) do
1593 if not self.override_dependencies
1594 and vs.enum_depends and vs.enum_depends[v.value] then
1595 for i, dep in ipairs(vs.enum_depends[v.value]) do
1596 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1599 self:value(v.value, v.title or v.value, unpack(deps))
1605 function ListValue.value(self, key, val, ...)
1606 if luci.util.contains(self.keylist, key) then
1611 table.insert(self.keylist, tostring(key))
1612 table.insert(self.vallist, tostring(val))
1614 for i, deps in ipairs({...}) do
1615 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1619 function ListValue.validate(self, val)
1620 if luci.util.contains(self.keylist, val) then
1630 MultiValue - Multiple delimited values
1631 widget: The widget that will be used (select, checkbox)
1632 delimiter: The delimiter that will separate the values (default: " ")
1634 MultiValue = class(AbstractValue)
1636 function MultiValue.__init__(self, ...)
1637 AbstractValue.__init__(self, ...)
1638 self.template = "cbi/mvalue"
1643 self.widget = "checkbox"
1644 self.delimiter = " "
1647 function MultiValue.render(self, ...)
1648 if self.widget == "select" and not self.size then
1649 self.size = #self.vallist
1652 AbstractValue.render(self, ...)
1655 function MultiValue.value(self, key, val)
1656 if luci.util.contains(self.keylist, key) then
1661 table.insert(self.keylist, tostring(key))
1662 table.insert(self.vallist, tostring(val))
1665 function MultiValue.valuelist(self, section)
1666 local val = self:cfgvalue(section)
1668 if not(type(val) == "string") then
1672 return luci.util.split(val, self.delimiter)
1675 function MultiValue.validate(self, val)
1676 val = (type(val) == "table") and val or {val}
1680 for i, value in ipairs(val) do
1681 if luci.util.contains(self.keylist, value) then
1682 result = result and (result .. self.delimiter .. value) or value
1690 StaticList = class(MultiValue)
1692 function StaticList.__init__(self, ...)
1693 MultiValue.__init__(self, ...)
1695 self.valuelist = self.cfgvalue
1697 if not self.override_scheme
1698 and self.map:get_scheme(self.section.sectiontype, self.option) then
1699 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1700 if self.value and vs.values and not self.override_values then
1701 for k, v in pairs(vs.values) do
1708 function StaticList.validate(self, value)
1709 value = (type(value) == "table") and value or {value}
1712 for i, v in ipairs(value) do
1713 if luci.util.contains(self.keylist, v) then
1714 table.insert(valid, v)
1721 DynamicList = class(AbstractValue)
1723 function DynamicList.__init__(self, ...)
1724 AbstractValue.__init__(self, ...)
1725 self.template = "cbi/dynlist"
1731 function DynamicList.value(self, key, val)
1733 table.insert(self.keylist, tostring(key))
1734 table.insert(self.vallist, tostring(val))
1737 function DynamicList.write(self, ...)
1738 self.map.proceed = true
1739 return AbstractValue.write(self, ...)
1742 function DynamicList.formvalue(self, section)
1743 local value = AbstractValue.formvalue(self, section)
1744 value = (type(value) == "table") and value or {value}
1747 for i, v in ipairs(value) do
1749 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1750 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1751 table.insert(valid, v)
1760 TextValue - A multi-line value
1763 TextValue = class(AbstractValue)
1765 function TextValue.__init__(self, ...)
1766 AbstractValue.__init__(self, ...)
1767 self.template = "cbi/tvalue"
1773 Button = class(AbstractValue)
1775 function Button.__init__(self, ...)
1776 AbstractValue.__init__(self, ...)
1777 self.template = "cbi/button"
1778 self.inputstyle = nil
1783 FileUpload = class(AbstractValue)
1785 function FileUpload.__init__(self, ...)
1786 AbstractValue.__init__(self, ...)
1787 self.template = "cbi/upload"
1788 if not self.map.upload_fields then
1789 self.map.upload_fields = { self }
1791 self.map.upload_fields[#self.map.upload_fields+1] = self
1795 function FileUpload.formcreated(self, section)
1796 return AbstractValue.formcreated(self, section) or
1797 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1798 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1801 function FileUpload.cfgvalue(self, section)
1802 local val = AbstractValue.cfgvalue(self, section)
1803 if val and fs.access(val) then
1809 function FileUpload.formvalue(self, section)
1810 local val = AbstractValue.formvalue(self, section)
1812 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1813 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1823 function FileUpload.remove(self, section)
1824 local val = AbstractValue.formvalue(self, section)
1825 if val and fs.access(val) then fs.unlink(val) end
1826 return AbstractValue.remove(self, section)
1830 FileBrowser = class(AbstractValue)
1832 function FileBrowser.__init__(self, ...)
1833 AbstractValue.__init__(self, ...)
1834 self.template = "cbi/browser"