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("cbi")
78 luci.i18n.loadc("uvl")
81 translate=i18n.translate,
82 translatef=i18n.translatef,
86 setfenv(func, setmetatable(env, {__index =
88 return rawget(tbl, key) or _M[key] or _G[key]
91 local maps = { func() }
93 local has_upload = false
95 for i, map in ipairs(maps) do
96 if not instanceof(map, Node) then
97 error("CBI map returns no valid map object!")
101 if map.upload_fields then
103 for _, field in ipairs(map.upload_fields) do
105 field.config .. '.' ..
106 field.section.sectiontype .. '.' ..
115 local uci = luci.model.uci.cursor()
116 local prm = luci.http.context.request.message.params
119 luci.http.setfilehandler(
120 function( field, chunk, eof )
121 if not field then return end
122 if field.name and not cbid then
123 local c, s, o = field.name:gmatch(
124 "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
127 if c and s and o then
128 local t = uci:get( c, s )
129 if t and uploads[c.."."..t.."."..o] then
130 local path = upldir .. field.name
131 fd = io.open(path, "w")
140 if field.name == cbid and fd then
156 local function _uvl_validate_section(node, name)
157 local co = node.map:get()
159 luci.uvl.STRICT_UNKNOWN_OPTIONS = false
160 luci.uvl.STRICT_UNKNOWN_SECTIONS = false
162 local function tag_fields(e)
163 if e.option and node.fields[e.option] then
164 if node.fields[e.option].error then
165 node.fields[e.option].error[name] = e
167 node.fields[e.option].error = { [name] = e }
170 for _, c in ipairs(e.childs) do tag_fields(c) end
174 local function tag_section(e)
176 for _, c in ipairs(e.childs or { e }) do
177 if c.childs and not c:is(luci.uvl.errors.ERR_DEPENDENCY) then
178 table.insert( s, c.childs[1]:string() )
180 table.insert( s, c:string() )
187 node.error = { [name] = s }
192 local stat, err = node.map.validator:validate_section(node.config, name, co)
194 node.map.save = false
201 local function _uvl_strip_remote_dependencies(deps)
204 for k, v in pairs(deps) do
205 k = k:gsub("%$config%.%$section%.", "")
206 if k:match("^[%w_]+$") and type(v) == "string" then
215 -- Node pseudo abstract class
218 function Node.__init__(self, title, description)
220 self.title = title or ""
221 self.description = description or ""
222 self.template = "cbi/node"
226 function Node._i18n(self, config, section, option, title, description)
229 if type(luci.i18n) == "table" then
231 local key = config and config:gsub("[^%w]+", "") or ""
233 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
234 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
236 self.title = title or luci.i18n.translate( key, option or section or config )
237 self.description = description or luci.i18n.translate( key .. "_desc", "" )
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, ...)
303 Node._i18n(self, config, nil, nil, ...)
306 self.parsechain = {self.config}
307 self.template = "cbi/map"
308 self.apply_on_parse = nil
309 self.readinput = true
313 self.uci = uci.cursor()
318 if not self.uci:load(self.config) then
319 error("Unable to read UCI data: " .. self.config)
322 self.validator = luci.uvl.UVL()
323 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)
361 if self:formvalue("cbi.skip") then
362 self.state = FORM_SKIP
363 return self:state_handler(self.state)
366 Node.parse(self, ...)
369 for i, config in ipairs(self.parsechain) do
370 self.uci:save(config)
372 if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
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 if self.apply_on_parse then
380 self.uci:apply(self.parsechain)
382 self._apply = function()
383 local cmd = self.uci:apply(self.parsechain, true)
389 Node.parse(self, true)
392 for i, config in ipairs(self.parsechain) do
393 self.uci:unload(config)
395 if type(self.commit_handler) == "function" then
396 self:commit_handler(self:submitstate())
400 if self:submitstate() then
401 if not self.save then
402 self.state = FORM_INVALID
403 elseif self.proceed then
404 self.state = FORM_PROCEED
406 self.state = self.changed and FORM_CHANGED or FORM_VALID
409 self.state = FORM_NODATA
412 return self:state_handler(self.state)
415 function Map.render(self, ...)
416 Node.render(self, ...)
418 local fp = self._apply()
424 -- Creates a child section
425 function Map.section(self, class, ...)
426 if instanceof(class, AbstractSection) then
427 local obj = class(self, ...)
431 error("class must be a descendent of AbstractSection")
436 function Map.add(self, sectiontype)
437 return self.uci:add(self.config, sectiontype)
441 function Map.set(self, section, option, value)
443 return self.uci:set(self.config, section, option, value)
445 return self.uci:set(self.config, section, value)
450 function Map.del(self, section, option)
452 return self.uci:delete(self.config, section, option)
454 return self.uci:delete(self.config, section)
459 function Map.get(self, section, option)
461 return self.uci:get_all(self.config)
463 return self.uci:get(self.config, section, option)
465 return self.uci:get_all(self.config, section)
472 Compound = class(Node)
474 function Compound.__init__(self, ...)
476 self.template = "cbi/compound"
477 self.children = {...}
480 function Compound.populate_delegator(self, delegator)
481 for _, v in ipairs(self.children) do
482 v.delegator = delegator
486 function Compound.parse(self, ...)
487 local cstate, state = 0
489 for k, child in ipairs(self.children) do
490 cstate = child:parse(...)
491 state = (not state or cstate < state) and cstate or state
499 Delegator - Node controller
501 Delegator = class(Node)
502 function Delegator.__init__(self, ...)
503 Node.__init__(self, ...)
505 self.defaultpath = {}
506 self.pageaction = false
507 self.readinput = true
508 self.allow_reset = false
509 self.allow_back = false
510 self.allow_finish = false
511 self.template = "cbi/delegator"
514 function Delegator.set(self, name, node)
515 if type(node) == "table" and getmetatable(node) == nil then
516 node = Compound(unpack(node))
518 assert(type(node) == "function" or instanceof(node, Compound), "Invalid")
519 assert(not self.nodes[name], "Duplicate entry")
521 self.nodes[name] = node
524 function Delegator.add(self, name, node)
525 node = self:set(name, node)
526 self.defaultpath[#self.defaultpath+1] = name
529 function Delegator.insert_after(self, name, after)
530 local n = #self.chain
531 for k, v in ipairs(self.chain) do
537 table.insert(self.chain, n, name)
540 function Delegator.set_route(self, ...)
541 local n, chain, route = 0, self.chain, {...}
543 if chain[i] == self.current then
552 for i = n + 1, #chain do
557 function Delegator.get(self, name)
558 return self.nodes[name]
561 function Delegator.parse(self, ...)
563 self.chain = self.chain or self:get_chain()
564 self.current = self.current or self:get_active()
565 self.active = self.active or self:get(self.current)
566 assert(self.active, "Invalid state")
568 local stat = FORM_DONE
569 if type(self.active) ~= "function" then
570 self.active:populate_delegator(self)
571 stat = self.active:parse()
576 if stat > FORM_PROCEED then
577 if Map.formvalue(self, "cbi.delg.back") then
578 newcurrent = self:get_prev(self.current)
580 newcurrent = self:get_next(self.current)
582 elseif stat < FORM_PROCEED then
587 if not Map.formvalue(self, "cbi.submit") then
589 elseif not newcurrent or not self:get(newcurrent) then
592 self.current = newcurrent
593 self.active = self:get(self.current)
594 if type(self.active) ~= "function" then
595 self.active:parse(false)
598 return self:parse(...)
603 function Delegator.get_next(self, state)
604 for k, v in ipairs(self.chain) do
606 return self.chain[k+1]
611 function Delegator.get_prev(self, state)
612 for k, v in ipairs(self.chain) do
614 return self.chain[k-1]
619 function Delegator.get_chain(self)
620 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
621 return type(x) == "table" and x or {x}
624 function Delegator.get_active(self)
625 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
633 Page.__init__ = Node.__init__
634 Page.parse = function() end
638 SimpleForm - A Simple non-UCI form
640 SimpleForm = class(Node)
642 function SimpleForm.__init__(self, config, title, description, data)
643 Node.__init__(self, title, description)
645 self.data = data or {}
646 self.template = "cbi/simpleform"
648 self.pageaction = false
649 self.readinput = true
652 SimpleForm.formvalue = Map.formvalue
653 SimpleForm.formvaluetable = Map.formvaluetable
655 function SimpleForm.parse(self, readinput, ...)
656 self.readinput = (readinput ~= false)
658 if self:formvalue("cbi.skip") then
662 if self:submitstate() then
663 Node.parse(self, 1, ...)
667 for k, j in ipairs(self.children) do
668 for i, v in ipairs(j.children) do
670 and (not v.tag_missing or not v.tag_missing[1])
671 and (not v.tag_invalid or not v.tag_invalid[1])
677 not self:submitstate() and FORM_NODATA
678 or valid and FORM_VALID
681 self.dorender = not self.handle
683 local nrender, nstate = self:handle(state, self.data)
684 self.dorender = self.dorender or (nrender ~= false)
685 state = nstate or state
690 function SimpleForm.render(self, ...)
691 if self.dorender then
692 Node.render(self, ...)
696 function SimpleForm.submitstate(self)
697 return self:formvalue("cbi.submit")
700 function SimpleForm.section(self, class, ...)
701 if instanceof(class, AbstractSection) then
702 local obj = class(self, ...)
706 error("class must be a descendent of AbstractSection")
710 -- Creates a child field
711 function SimpleForm.field(self, class, ...)
713 for k, v in ipairs(self.children) do
714 if instanceof(v, SimpleSection) then
720 section = self:section(SimpleSection)
723 if instanceof(class, AbstractValue) then
724 local obj = class(self, section, ...)
725 obj.track_missing = true
729 error("class must be a descendent of AbstractValue")
733 function SimpleForm.set(self, section, option, value)
734 self.data[option] = value
738 function SimpleForm.del(self, section, option)
739 self.data[option] = nil
743 function SimpleForm.get(self, section, option)
744 return self.data[option]
748 function SimpleForm.get_scheme()
753 Form = class(SimpleForm)
755 function Form.__init__(self, ...)
756 SimpleForm.__init__(self, ...)
764 AbstractSection = class(Node)
766 function AbstractSection.__init__(self, map, sectiontype, ...)
767 Node.__init__(self, ...)
768 self.sectiontype = sectiontype
770 self.config = map.config
775 self.tag_invalid = {}
776 self.tag_deperror = {}
780 self.addremove = false
784 -- Define a tab for the section
785 function AbstractSection.tab(self, tab, title, desc)
786 self.tabs = self.tabs or { }
787 self.tab_names = self.tab_names or { }
789 self.tab_names[#self.tab_names+1] = tab
797 -- Appends a new option
798 function AbstractSection.option(self, class, option, ...)
799 -- Autodetect from UVL
800 if class == true and self.map:get_scheme(self.sectiontype, option) then
801 local vs = self.map:get_scheme(self.sectiontype, option)
802 if vs.type == "boolean" then
804 elseif vs.type == "list" then
806 elseif vs.type == "enum" or vs.type == "reference" then
813 if instanceof(class, AbstractValue) then
814 local obj = class(self.map, self, option, ...)
816 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
819 self.fields[option] = obj
821 elseif class == true then
822 error("No valid class was given and autodetection failed.")
824 error("class must be a descendant of AbstractValue")
828 -- Appends a new tabbed option
829 function AbstractSection.taboption(self, tab, ...)
831 assert(tab and self.tabs and self.tabs[tab],
832 "Cannot assign option to not existing tab %q" % tostring(tab))
834 local l = self.tabs[tab].childs
835 local o = AbstractSection.option(self, ...)
837 if o then l[#l+1] = o end
842 -- Render a single tab
843 function AbstractSection.render_tab(self, tab, ...)
845 assert(tab and self.tabs and self.tabs[tab],
846 "Cannot render not existing tab %q" % tostring(tab))
848 for _, node in ipairs(self.tabs[tab].childs) do
853 -- Parse optional options
854 function AbstractSection.parse_optionals(self, section)
855 if not self.optional then
859 self.optionals[section] = {}
861 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
862 for k,v in ipairs(self.children) do
863 if v.optional and not v:cfgvalue(section) then
864 if field == v.option then
866 self.map.proceed = true
868 table.insert(self.optionals[section], v)
873 if field and #field > 0 and self.dynamic then
874 self:add_dynamic(field)
878 -- Add a dynamic option
879 function AbstractSection.add_dynamic(self, field, optional)
880 local o = self:option(Value, field, field)
881 o.optional = optional
884 -- Parse all dynamic options
885 function AbstractSection.parse_dynamic(self, section)
886 if not self.dynamic then
890 local arr = luci.util.clone(self:cfgvalue(section))
891 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
892 for k, v in pairs(form) do
896 for key,val in pairs(arr) do
899 for i,c in ipairs(self.children) do
900 if c.option == key then
905 if create and key:sub(1, 1) ~= "." then
906 self.map.proceed = true
907 self:add_dynamic(key, true)
912 -- Returns the section's UCI table
913 function AbstractSection.cfgvalue(self, section)
914 return self.map:get(section)
918 function AbstractSection.push_events(self)
919 --luci.util.append(self.map.events, self.events)
920 self.map.changed = true
923 -- Removes the section
924 function AbstractSection.remove(self, section)
925 self.map.proceed = true
926 return self.map:del(section)
929 -- Creates the section
930 function AbstractSection.create(self, section)
934 stat = section:match("^%w+$") and self.map:set(section, nil, self.sectiontype)
936 section = self.map:add(self.sectiontype)
941 for k,v in pairs(self.children) do
943 self.map:set(section, v.option, v.default)
947 for k,v in pairs(self.defaults) do
948 self.map:set(section, k, v)
952 self.map.proceed = true
958 SimpleSection = class(AbstractSection)
960 function SimpleSection.__init__(self, form, ...)
961 AbstractSection.__init__(self, form, nil, ...)
962 self.template = "cbi/nullsection"
966 Table = class(AbstractSection)
968 function Table.__init__(self, form, data, ...)
969 local datasource = {}
971 datasource.config = "table"
972 self.data = data or {}
974 datasource.formvalue = Map.formvalue
975 datasource.formvaluetable = Map.formvaluetable
976 datasource.readinput = true
978 function datasource.get(self, section, option)
979 return tself.data[section] and tself.data[section][option]
982 function datasource.submitstate(self)
983 return Map.formvalue(self, "cbi.submit")
986 function datasource.del(...)
990 function datasource.get_scheme()
994 AbstractSection.__init__(self, datasource, "table", ...)
995 self.template = "cbi/tblsection"
996 self.rowcolors = true
997 self.anonymous = true
1000 function Table.parse(self, readinput)
1001 self.map.readinput = (readinput ~= false)
1002 for i, k in ipairs(self:cfgsections()) do
1003 if self.map:submitstate() then
1009 function Table.cfgsections(self)
1012 for i, v in luci.util.kspairs(self.data) do
1013 table.insert(sections, i)
1019 function Table.update(self, data)
1026 NamedSection - A fixed configuration section defined by its name
1028 NamedSection = class(AbstractSection)
1030 function NamedSection.__init__(self, map, section, stype, ...)
1031 AbstractSection.__init__(self, map, stype, ...)
1032 Node._i18n(self, map.config, section, nil, ...)
1035 self.addremove = false
1037 -- Use defaults from UVL
1038 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1039 local vs = self.map:get_scheme(self.sectiontype)
1040 self.addremove = not vs.unique and not vs.required
1041 self.dynamic = vs.dynamic
1042 self.title = self.title or vs.title
1043 self.description = self.description or vs.descr
1046 self.template = "cbi/nsection"
1047 self.section = section
1050 function NamedSection.parse(self, novld)
1051 local s = self.section
1052 local active = self:cfgvalue(s)
1054 if self.addremove then
1055 local path = self.config.."."..s
1056 if active then -- Remove the section
1057 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1061 else -- Create and apply default values
1062 if self.map:formvalue("cbi.cns."..path) then
1070 AbstractSection.parse_dynamic(self, s)
1071 if self.map:submitstate() then
1074 if not novld and not self.override_scheme and self.map.scheme then
1075 _uvl_validate_section(self, s)
1078 AbstractSection.parse_optionals(self, s)
1080 if self.changed then
1088 TypedSection - A (set of) configuration section(s) defined by the type
1089 addremove: Defines whether the user can add/remove sections of this type
1090 anonymous: Allow creating anonymous sections
1091 validate: a validation function returning nil if the section is invalid
1093 TypedSection = class(AbstractSection)
1095 function TypedSection.__init__(self, map, type, ...)
1096 AbstractSection.__init__(self, map, type, ...)
1097 Node._i18n(self, map.config, type, nil, ...)
1099 self.template = "cbi/tsection"
1101 self.anonymous = false
1103 -- Use defaults from UVL
1104 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1105 local vs = self.map:get_scheme(self.sectiontype)
1106 self.addremove = not vs.unique and not vs.required
1107 self.dynamic = vs.dynamic
1108 self.anonymous = not vs.named
1109 self.title = self.title or vs.title
1110 self.description = self.description or vs.descr
1114 -- Return all matching UCI sections for this TypedSection
1115 function TypedSection.cfgsections(self)
1117 self.map.uci:foreach(self.map.config, self.sectiontype,
1119 if self:checkscope(section[".name"]) then
1120 table.insert(sections, section[".name"])
1127 -- Limits scope to sections that have certain option => value pairs
1128 function TypedSection.depends(self, option, value)
1129 table.insert(self.deps, {option=option, value=value})
1132 function TypedSection.parse(self, novld)
1133 if self.addremove then
1135 local crval = REMOVE_PREFIX .. self.config
1136 local name = self.map:formvaluetable(crval)
1137 for k,v in pairs(name) do
1138 if k:sub(-2) == ".x" then
1139 k = k:sub(1, #k - 2)
1141 if self:cfgvalue(k) and self:checkscope(k) then
1148 for i, k in ipairs(self:cfgsections()) do
1149 AbstractSection.parse_dynamic(self, k)
1150 if self.map:submitstate() then
1151 Node.parse(self, k, novld)
1153 if not novld and not self.override_scheme and self.map.scheme then
1154 _uvl_validate_section(self, k)
1157 AbstractSection.parse_optionals(self, k)
1160 if self.addremove then
1163 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1164 local name = self.map:formvalue(crval)
1165 if self.anonymous then
1167 created = self:create()
1171 -- Ignore if it already exists
1172 if self:cfgvalue(name) then
1176 name = self:checkscope(name)
1179 self.err_invalid = true
1182 if name and #name > 0 then
1183 created = self:create(name) and name
1185 self.invalid_cts = true
1192 AbstractSection.parse_optionals(self, created)
1196 if created or self.changed then
1201 -- Verifies scope of sections
1202 function TypedSection.checkscope(self, section)
1203 -- Check if we are not excluded
1204 if self.filter and not self:filter(section) then
1208 -- Check if at least one dependency is met
1209 if #self.deps > 0 and self:cfgvalue(section) then
1212 for k, v in ipairs(self.deps) do
1213 if self:cfgvalue(section)[v.option] == v.value then
1223 return self:validate(section)
1227 -- Dummy validate function
1228 function TypedSection.validate(self, section)
1234 AbstractValue - An abstract Value Type
1235 null: Value can be empty
1236 valid: A function returning the value if it is valid otherwise nil
1237 depends: A table of option => value pairs of which one must be true
1238 default: The default value
1239 size: The size of the input fields
1240 rmempty: Unset value if empty
1241 optional: This value is optional (see AbstractSection.optionals)
1243 AbstractValue = class(Node)
1245 function AbstractValue.__init__(self, map, section, option, ...)
1246 Node.__init__(self, ...)
1247 self.section = section
1248 self.option = option
1250 self.config = map.config
1251 self.tag_invalid = {}
1252 self.tag_missing = {}
1253 self.tag_reqerror = {}
1256 --self.cast = "string"
1258 self.track_missing = false
1262 self.optional = false
1265 function AbstractValue.prepare(self)
1266 -- Use defaults from UVL
1267 if not self.override_scheme
1268 and self.map:get_scheme(self.section.sectiontype, self.option) then
1269 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1270 if self.cast == nil then
1271 self.cast = (vs.type == "list") and "list" or "string"
1273 self.title = self.title or vs.title
1274 self.description = self.description or vs.descr
1275 if self.default == nil then
1276 self.default = vs.default
1279 if vs.depends and not self.override_dependencies then
1280 for i, deps in ipairs(vs.depends) do
1281 deps = _uvl_strip_remote_dependencies(deps)
1289 self.cast = self.cast or "string"
1292 -- Add a dependencie to another section field
1293 function AbstractValue.depends(self, field, value)
1295 if type(field) == "string" then
1302 table.insert(self.deps, {deps=deps, add=""})
1305 -- Generates the unique CBID
1306 function AbstractValue.cbid(self, section)
1307 return "cbid."..self.map.config.."."..section.."."..self.option
1310 -- Return whether this object should be created
1311 function AbstractValue.formcreated(self, section)
1312 local key = "cbi.opt."..self.config.."."..section
1313 return (self.map:formvalue(key) == self.option)
1316 -- Returns the formvalue for this object
1317 function AbstractValue.formvalue(self, section)
1318 return self.map:formvalue(self:cbid(section))
1321 function AbstractValue.additional(self, value)
1322 self.optional = value
1325 function AbstractValue.mandatory(self, value)
1326 self.rmempty = not value
1329 function AbstractValue.parse(self, section, novld)
1330 local fvalue = self:formvalue(section)
1331 local cvalue = self:cfgvalue(section)
1333 -- If favlue and cvalue are both tables and have the same content
1334 -- make them identical
1335 if type(fvalue) == "table" and type(cvalue) == "table" then
1336 local equal = #fvalue == #cvalue
1339 if cvalue[i] ~= fvalue[i] then
1349 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1350 fvalue = self:transform(self:validate(fvalue, section))
1351 if not fvalue and not novld then
1353 self.error[section] = "invalid"
1355 self.error = { [section] = "invalid" }
1357 if self.section.error then
1358 table.insert(self.section.error[section], "invalid")
1360 self.section.error = {[section] = {"invalid"}}
1362 self.map.save = false
1364 if fvalue and not (fvalue == cvalue) then
1365 if self:write(section, fvalue) then
1367 self.section.changed = true
1368 --luci.util.append(self.map.events, self.events)
1371 else -- Unset the UCI or error
1372 if self.rmempty or self.optional then
1373 if self:remove(section) then
1375 self.section.changed = true
1376 --luci.util.append(self.map.events, self.events)
1378 elseif cvalue ~= fvalue and not novld then
1379 self:write(section, fvalue or "")
1381 self.error[section] = "missing"
1383 self.error = { [section] = "missing" }
1385 self.map.save = false
1390 -- Render if this value exists or if it is mandatory
1391 function AbstractValue.render(self, s, scope)
1392 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1395 scope.cbid = self:cbid(s)
1396 scope.striptags = luci.util.striptags
1397 scope.pcdata = luci.util.pcdata
1399 scope.ifattr = function(cond,key,val)
1401 return string.format(
1402 ' %s="%s"', tostring(key),
1403 luci.util.pcdata(tostring( val
1405 or (type(self[key]) ~= "function" and self[key])
1413 scope.attr = function(...)
1414 return scope.ifattr( true, ... )
1417 Node.render(self, scope)
1421 -- Return the UCI value of this object
1422 function AbstractValue.cfgvalue(self, section)
1423 local value = self.map:get(section, self.option)
1426 elseif not self.cast or self.cast == type(value) then
1428 elseif self.cast == "string" then
1429 if type(value) == "table" then
1432 elseif self.cast == "table" then
1433 return luci.util.split(value, "%s+", nil, true)
1437 -- Validate the form value
1438 function AbstractValue.validate(self, value)
1442 AbstractValue.transform = AbstractValue.validate
1446 function AbstractValue.write(self, section, value)
1447 return self.map:set(section, self.option, value)
1451 function AbstractValue.remove(self, section)
1452 return self.map:del(section, self.option)
1459 Value - A one-line value
1460 maxlength: The maximum length
1462 Value = class(AbstractValue)
1464 function Value.__init__(self, ...)
1465 AbstractValue.__init__(self, ...)
1466 self.template = "cbi/value"
1471 function Value.value(self, key, val)
1473 table.insert(self.keylist, tostring(key))
1474 table.insert(self.vallist, tostring(val))
1478 -- DummyValue - This does nothing except being there
1479 DummyValue = class(AbstractValue)
1481 function DummyValue.__init__(self, ...)
1482 AbstractValue.__init__(self, ...)
1483 self.template = "cbi/dvalue"
1487 function DummyValue.cfgvalue(self, section)
1490 if type(self.value) == "function" then
1491 value = self:value(section)
1496 value = AbstractValue.cfgvalue(self, section)
1501 function DummyValue.parse(self)
1507 Flag - A flag being enabled or disabled
1509 Flag = class(AbstractValue)
1511 function Flag.__init__(self, ...)
1512 AbstractValue.__init__(self, ...)
1513 self.template = "cbi/fvalue"
1519 -- A flag can only have two states: set or unset
1520 function Flag.parse(self, section)
1521 local fvalue = self:formvalue(section)
1524 fvalue = self.enabled
1526 fvalue = self.disabled
1529 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1530 if not(fvalue == self:cfgvalue(section)) then
1531 self:write(section, fvalue)
1534 self:remove(section)
1541 ListValue - A one-line value predefined in a list
1542 widget: The widget that will be used (select, radio)
1544 ListValue = class(AbstractValue)
1546 function ListValue.__init__(self, ...)
1547 AbstractValue.__init__(self, ...)
1548 self.template = "cbi/lvalue"
1553 self.widget = "select"
1556 function ListValue.prepare(self, ...)
1557 AbstractValue.prepare(self, ...)
1558 if not self.override_scheme
1559 and self.map:get_scheme(self.section.sectiontype, self.option) then
1560 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1561 if self.value and vs.valuelist and not self.override_values then
1562 for k, v in ipairs(vs.valuelist) do
1564 if not self.override_dependencies
1565 and vs.enum_depends and vs.enum_depends[v.value] then
1566 for i, dep in ipairs(vs.enum_depends[v.value]) do
1567 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1570 self:value(v.value, v.title or v.value, unpack(deps))
1576 function ListValue.value(self, key, val, ...)
1577 if luci.util.contains(self.keylist, key) then
1582 table.insert(self.keylist, tostring(key))
1583 table.insert(self.vallist, tostring(val))
1585 for i, deps in ipairs({...}) do
1586 table.insert(self.deps, {add = "-"..key, deps=deps})
1590 function ListValue.validate(self, val)
1591 if luci.util.contains(self.keylist, val) then
1601 MultiValue - Multiple delimited values
1602 widget: The widget that will be used (select, checkbox)
1603 delimiter: The delimiter that will separate the values (default: " ")
1605 MultiValue = class(AbstractValue)
1607 function MultiValue.__init__(self, ...)
1608 AbstractValue.__init__(self, ...)
1609 self.template = "cbi/mvalue"
1614 self.widget = "checkbox"
1615 self.delimiter = " "
1618 function MultiValue.render(self, ...)
1619 if self.widget == "select" and not self.size then
1620 self.size = #self.vallist
1623 AbstractValue.render(self, ...)
1626 function MultiValue.value(self, key, val)
1627 if luci.util.contains(self.keylist, key) then
1632 table.insert(self.keylist, tostring(key))
1633 table.insert(self.vallist, tostring(val))
1636 function MultiValue.valuelist(self, section)
1637 local val = self:cfgvalue(section)
1639 if not(type(val) == "string") then
1643 return luci.util.split(val, self.delimiter)
1646 function MultiValue.validate(self, val)
1647 val = (type(val) == "table") and val or {val}
1651 for i, value in ipairs(val) do
1652 if luci.util.contains(self.keylist, value) then
1653 result = result and (result .. self.delimiter .. value) or value
1661 StaticList = class(MultiValue)
1663 function StaticList.__init__(self, ...)
1664 MultiValue.__init__(self, ...)
1666 self.valuelist = self.cfgvalue
1668 if not self.override_scheme
1669 and self.map:get_scheme(self.section.sectiontype, self.option) then
1670 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1671 if self.value and vs.values and not self.override_values then
1672 for k, v in pairs(vs.values) do
1679 function StaticList.validate(self, value)
1680 value = (type(value) == "table") and value or {value}
1683 for i, v in ipairs(value) do
1684 if luci.util.contains(self.keylist, v) then
1685 table.insert(valid, v)
1692 DynamicList = class(AbstractValue)
1694 function DynamicList.__init__(self, ...)
1695 AbstractValue.__init__(self, ...)
1696 self.template = "cbi/dynlist"
1702 function DynamicList.value(self, key, val)
1704 table.insert(self.keylist, tostring(key))
1705 table.insert(self.vallist, tostring(val))
1708 function DynamicList.write(self, ...)
1709 self.map.proceed = true
1710 return AbstractValue.write(self, ...)
1713 function DynamicList.formvalue(self, section)
1714 local value = AbstractValue.formvalue(self, section)
1715 value = (type(value) == "table") and value or {value}
1718 for i, v in ipairs(value) do
1720 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1721 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1722 table.insert(valid, v)
1731 TextValue - A multi-line value
1734 TextValue = class(AbstractValue)
1736 function TextValue.__init__(self, ...)
1737 AbstractValue.__init__(self, ...)
1738 self.template = "cbi/tvalue"
1744 Button = class(AbstractValue)
1746 function Button.__init__(self, ...)
1747 AbstractValue.__init__(self, ...)
1748 self.template = "cbi/button"
1749 self.inputstyle = nil
1754 FileUpload = class(AbstractValue)
1756 function FileUpload.__init__(self, ...)
1757 AbstractValue.__init__(self, ...)
1758 self.template = "cbi/upload"
1759 if not self.map.upload_fields then
1760 self.map.upload_fields = { self }
1762 self.map.upload_fields[#self.map.upload_fields+1] = self
1766 function FileUpload.formcreated(self, section)
1767 return AbstractValue.formcreated(self, section) or
1768 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1769 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1772 function FileUpload.cfgvalue(self, section)
1773 local val = AbstractValue.cfgvalue(self, section)
1774 if val and fs.access(val) then
1780 function FileUpload.formvalue(self, section)
1781 local val = AbstractValue.formvalue(self, section)
1783 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1784 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1794 function FileUpload.remove(self, section)
1795 local val = AbstractValue.formvalue(self, section)
1796 if val and fs.access(val) then fs.unlink(val) end
1797 return AbstractValue.remove(self, section)
1801 FileBrowser = class(AbstractValue)
1803 function FileBrowser.__init__(self, ...)
1804 AbstractValue.__init__(self, ...)
1805 self.template = "cbi/browser"