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_cancel = false
510 self.allow_back = false
511 self.allow_finish = false
512 self.template = "cbi/delegator"
515 function Delegator.set(self, name, node)
516 if type(node) == "table" and getmetatable(node) == nil then
517 node = Compound(unpack(node))
519 assert(type(node) == "function" or instanceof(node, Compound), "Invalid")
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
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 return self.nodes[name]
562 function Delegator.parse(self, ...)
563 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
564 if self.on_cancel then
570 if self.on_init and not Map.formvalue(self, "cbi.delg.current") then
575 self.chain = self.chain or self:get_chain()
576 self.current = self.current or self:get_active()
577 self.active = self.active or self:get(self.current)
578 assert(self.active, "Invalid state")
580 local stat = FORM_DONE
581 if type(self.active) ~= "function" then
582 self.active:populate_delegator(self)
583 stat = self.active:parse()
588 if stat > FORM_PROCEED then
589 if Map.formvalue(self, "cbi.delg.back") then
590 newcurrent = self:get_prev(self.current)
592 newcurrent = self:get_next(self.current)
594 elseif stat < FORM_PROCEED then
599 if not Map.formvalue(self, "cbi.submit") then
601 elseif not newcurrent or not self:get(newcurrent) then
607 self.current = newcurrent
608 self.active = self:get(self.current)
609 if type(self.active) ~= "function" then
610 self.active:parse(false)
613 return self:parse(...)
618 function Delegator.get_next(self, state)
619 for k, v in ipairs(self.chain) do
621 return self.chain[k+1]
626 function Delegator.get_prev(self, state)
627 for k, v in ipairs(self.chain) do
629 return self.chain[k-1]
634 function Delegator.get_chain(self)
635 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
636 return type(x) == "table" and x or {x}
639 function Delegator.get_active(self)
640 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
648 Page.__init__ = Node.__init__
649 Page.parse = function() end
653 SimpleForm - A Simple non-UCI form
655 SimpleForm = class(Node)
657 function SimpleForm.__init__(self, config, title, description, data)
658 Node.__init__(self, title, description)
660 self.data = data or {}
661 self.template = "cbi/simpleform"
663 self.pageaction = false
664 self.readinput = true
667 SimpleForm.formvalue = Map.formvalue
668 SimpleForm.formvaluetable = Map.formvaluetable
670 function SimpleForm.parse(self, readinput, ...)
671 self.readinput = (readinput ~= false)
673 if self:formvalue("cbi.skip") then
677 if self:submitstate() then
678 Node.parse(self, 1, ...)
682 for k, j in ipairs(self.children) do
683 for i, v in ipairs(j.children) do
685 and (not v.tag_missing or not v.tag_missing[1])
686 and (not v.tag_invalid or not v.tag_invalid[1])
692 not self:submitstate() and FORM_NODATA
693 or valid and FORM_VALID
696 self.dorender = not self.handle
698 local nrender, nstate = self:handle(state, self.data)
699 self.dorender = self.dorender or (nrender ~= false)
700 state = nstate or state
705 function SimpleForm.render(self, ...)
706 if self.dorender then
707 Node.render(self, ...)
711 function SimpleForm.submitstate(self)
712 return self:formvalue("cbi.submit")
715 function SimpleForm.section(self, class, ...)
716 if instanceof(class, AbstractSection) then
717 local obj = class(self, ...)
721 error("class must be a descendent of AbstractSection")
725 -- Creates a child field
726 function SimpleForm.field(self, class, ...)
728 for k, v in ipairs(self.children) do
729 if instanceof(v, SimpleSection) then
735 section = self:section(SimpleSection)
738 if instanceof(class, AbstractValue) then
739 local obj = class(self, section, ...)
740 obj.track_missing = true
744 error("class must be a descendent of AbstractValue")
748 function SimpleForm.set(self, section, option, value)
749 self.data[option] = value
753 function SimpleForm.del(self, section, option)
754 self.data[option] = nil
758 function SimpleForm.get(self, section, option)
759 return self.data[option]
763 function SimpleForm.get_scheme()
768 Form = class(SimpleForm)
770 function Form.__init__(self, ...)
771 SimpleForm.__init__(self, ...)
779 AbstractSection = class(Node)
781 function AbstractSection.__init__(self, map, sectiontype, ...)
782 Node.__init__(self, ...)
783 self.sectiontype = sectiontype
785 self.config = map.config
790 self.tag_invalid = {}
791 self.tag_deperror = {}
795 self.addremove = false
799 -- Define a tab for the section
800 function AbstractSection.tab(self, tab, title, desc)
801 self.tabs = self.tabs or { }
802 self.tab_names = self.tab_names or { }
804 self.tab_names[#self.tab_names+1] = tab
812 -- Appends a new option
813 function AbstractSection.option(self, class, option, ...)
814 -- Autodetect from UVL
815 if class == true and self.map:get_scheme(self.sectiontype, option) then
816 local vs = self.map:get_scheme(self.sectiontype, option)
817 if vs.type == "boolean" then
819 elseif vs.type == "list" then
821 elseif vs.type == "enum" or vs.type == "reference" then
828 if instanceof(class, AbstractValue) then
829 local obj = class(self.map, self, option, ...)
831 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
834 self.fields[option] = obj
836 elseif class == true then
837 error("No valid class was given and autodetection failed.")
839 error("class must be a descendant of AbstractValue")
843 -- Appends a new tabbed option
844 function AbstractSection.taboption(self, tab, ...)
846 assert(tab and self.tabs and self.tabs[tab],
847 "Cannot assign option to not existing tab %q" % tostring(tab))
849 local l = self.tabs[tab].childs
850 local o = AbstractSection.option(self, ...)
852 if o then l[#l+1] = o end
857 -- Render a single tab
858 function AbstractSection.render_tab(self, tab, ...)
860 assert(tab and self.tabs and self.tabs[tab],
861 "Cannot render not existing tab %q" % tostring(tab))
863 for _, node in ipairs(self.tabs[tab].childs) do
868 -- Parse optional options
869 function AbstractSection.parse_optionals(self, section)
870 if not self.optional then
874 self.optionals[section] = {}
876 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
877 for k,v in ipairs(self.children) do
878 if v.optional and not v:cfgvalue(section) then
879 if field == v.option then
881 self.map.proceed = true
883 table.insert(self.optionals[section], v)
888 if field and #field > 0 and self.dynamic then
889 self:add_dynamic(field)
893 -- Add a dynamic option
894 function AbstractSection.add_dynamic(self, field, optional)
895 local o = self:option(Value, field, field)
896 o.optional = optional
899 -- Parse all dynamic options
900 function AbstractSection.parse_dynamic(self, section)
901 if not self.dynamic then
905 local arr = luci.util.clone(self:cfgvalue(section))
906 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
907 for k, v in pairs(form) do
911 for key,val in pairs(arr) do
914 for i,c in ipairs(self.children) do
915 if c.option == key then
920 if create and key:sub(1, 1) ~= "." then
921 self.map.proceed = true
922 self:add_dynamic(key, true)
927 -- Returns the section's UCI table
928 function AbstractSection.cfgvalue(self, section)
929 return self.map:get(section)
933 function AbstractSection.push_events(self)
934 --luci.util.append(self.map.events, self.events)
935 self.map.changed = true
938 -- Removes the section
939 function AbstractSection.remove(self, section)
940 self.map.proceed = true
941 return self.map:del(section)
944 -- Creates the section
945 function AbstractSection.create(self, section)
949 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
951 section = self.map:add(self.sectiontype)
956 for k,v in pairs(self.children) do
958 self.map:set(section, v.option, v.default)
962 for k,v in pairs(self.defaults) do
963 self.map:set(section, k, v)
967 self.map.proceed = true
973 SimpleSection = class(AbstractSection)
975 function SimpleSection.__init__(self, form, ...)
976 AbstractSection.__init__(self, form, nil, ...)
977 self.template = "cbi/nullsection"
981 Table = class(AbstractSection)
983 function Table.__init__(self, form, data, ...)
984 local datasource = {}
986 datasource.config = "table"
987 self.data = data or {}
989 datasource.formvalue = Map.formvalue
990 datasource.formvaluetable = Map.formvaluetable
991 datasource.readinput = true
993 function datasource.get(self, section, option)
994 return tself.data[section] and tself.data[section][option]
997 function datasource.submitstate(self)
998 return Map.formvalue(self, "cbi.submit")
1001 function datasource.del(...)
1005 function datasource.get_scheme()
1009 AbstractSection.__init__(self, datasource, "table", ...)
1010 self.template = "cbi/tblsection"
1011 self.rowcolors = true
1012 self.anonymous = true
1015 function Table.parse(self, readinput)
1016 self.map.readinput = (readinput ~= false)
1017 for i, k in ipairs(self:cfgsections()) do
1018 if self.map:submitstate() then
1024 function Table.cfgsections(self)
1027 for i, v in luci.util.kspairs(self.data) do
1028 table.insert(sections, i)
1034 function Table.update(self, data)
1041 NamedSection - A fixed configuration section defined by its name
1043 NamedSection = class(AbstractSection)
1045 function NamedSection.__init__(self, map, section, stype, ...)
1046 AbstractSection.__init__(self, map, stype, ...)
1047 Node._i18n(self, map.config, section, nil, ...)
1050 self.addremove = false
1052 -- Use defaults from UVL
1053 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1054 local vs = self.map:get_scheme(self.sectiontype)
1055 self.addremove = not vs.unique and not vs.required
1056 self.dynamic = vs.dynamic
1057 self.title = self.title or vs.title
1058 self.description = self.description or vs.descr
1061 self.template = "cbi/nsection"
1062 self.section = section
1065 function NamedSection.parse(self, novld)
1066 local s = self.section
1067 local active = self:cfgvalue(s)
1069 if self.addremove then
1070 local path = self.config.."."..s
1071 if active then -- Remove the section
1072 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1076 else -- Create and apply default values
1077 if self.map:formvalue("cbi.cns."..path) then
1085 AbstractSection.parse_dynamic(self, s)
1086 if self.map:submitstate() then
1089 if not novld and not self.override_scheme and self.map.scheme then
1090 _uvl_validate_section(self, s)
1093 AbstractSection.parse_optionals(self, s)
1095 if self.changed then
1103 TypedSection - A (set of) configuration section(s) defined by the type
1104 addremove: Defines whether the user can add/remove sections of this type
1105 anonymous: Allow creating anonymous sections
1106 validate: a validation function returning nil if the section is invalid
1108 TypedSection = class(AbstractSection)
1110 function TypedSection.__init__(self, map, type, ...)
1111 AbstractSection.__init__(self, map, type, ...)
1112 Node._i18n(self, map.config, type, nil, ...)
1114 self.template = "cbi/tsection"
1116 self.anonymous = false
1118 -- Use defaults from UVL
1119 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1120 local vs = self.map:get_scheme(self.sectiontype)
1121 self.addremove = not vs.unique and not vs.required
1122 self.dynamic = vs.dynamic
1123 self.anonymous = not vs.named
1124 self.title = self.title or vs.title
1125 self.description = self.description or vs.descr
1129 -- Return all matching UCI sections for this TypedSection
1130 function TypedSection.cfgsections(self)
1132 self.map.uci:foreach(self.map.config, self.sectiontype,
1134 if self:checkscope(section[".name"]) then
1135 table.insert(sections, section[".name"])
1142 -- Limits scope to sections that have certain option => value pairs
1143 function TypedSection.depends(self, option, value)
1144 table.insert(self.deps, {option=option, value=value})
1147 function TypedSection.parse(self, novld)
1148 if self.addremove then
1150 local crval = REMOVE_PREFIX .. self.config
1151 local name = self.map:formvaluetable(crval)
1152 for k,v in pairs(name) do
1153 if k:sub(-2) == ".x" then
1154 k = k:sub(1, #k - 2)
1156 if self:cfgvalue(k) and self:checkscope(k) then
1163 for i, k in ipairs(self:cfgsections()) do
1164 AbstractSection.parse_dynamic(self, k)
1165 if self.map:submitstate() then
1166 Node.parse(self, k, novld)
1168 if not novld and not self.override_scheme and self.map.scheme then
1169 _uvl_validate_section(self, k)
1172 AbstractSection.parse_optionals(self, k)
1175 if self.addremove then
1178 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1179 local name = self.map:formvalue(crval)
1180 if self.anonymous then
1182 created = self:create()
1186 -- Ignore if it already exists
1187 if self:cfgvalue(name) then
1191 name = self:checkscope(name)
1194 self.err_invalid = true
1197 if name and #name > 0 then
1198 created = self:create(name) and name
1200 self.invalid_cts = true
1207 AbstractSection.parse_optionals(self, created)
1211 if created or self.changed then
1216 -- Verifies scope of sections
1217 function TypedSection.checkscope(self, section)
1218 -- Check if we are not excluded
1219 if self.filter and not self:filter(section) then
1223 -- Check if at least one dependency is met
1224 if #self.deps > 0 and self:cfgvalue(section) then
1227 for k, v in ipairs(self.deps) do
1228 if self:cfgvalue(section)[v.option] == v.value then
1238 return self:validate(section)
1242 -- Dummy validate function
1243 function TypedSection.validate(self, section)
1249 AbstractValue - An abstract Value Type
1250 null: Value can be empty
1251 valid: A function returning the value if it is valid otherwise nil
1252 depends: A table of option => value pairs of which one must be true
1253 default: The default value
1254 size: The size of the input fields
1255 rmempty: Unset value if empty
1256 optional: This value is optional (see AbstractSection.optionals)
1258 AbstractValue = class(Node)
1260 function AbstractValue.__init__(self, map, section, option, ...)
1261 Node.__init__(self, ...)
1262 self.section = section
1263 self.option = option
1265 self.config = map.config
1266 self.tag_invalid = {}
1267 self.tag_missing = {}
1268 self.tag_reqerror = {}
1271 --self.cast = "string"
1273 self.track_missing = false
1277 self.optional = false
1280 function AbstractValue.prepare(self)
1281 -- Use defaults from UVL
1282 if not self.override_scheme
1283 and self.map:get_scheme(self.section.sectiontype, self.option) then
1284 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1285 if self.cast == nil then
1286 self.cast = (vs.type == "list") and "list" or "string"
1288 self.title = self.title or vs.title
1289 self.description = self.description or vs.descr
1290 if self.default == nil then
1291 self.default = vs.default
1294 if vs.depends and not self.override_dependencies then
1295 for i, deps in ipairs(vs.depends) do
1296 deps = _uvl_strip_remote_dependencies(deps)
1304 self.cast = self.cast or "string"
1307 -- Add a dependencie to another section field
1308 function AbstractValue.depends(self, field, value)
1310 if type(field) == "string" then
1317 table.insert(self.deps, {deps=deps, add=""})
1320 -- Generates the unique CBID
1321 function AbstractValue.cbid(self, section)
1322 return "cbid."..self.map.config.."."..section.."."..self.option
1325 -- Return whether this object should be created
1326 function AbstractValue.formcreated(self, section)
1327 local key = "cbi.opt."..self.config.."."..section
1328 return (self.map:formvalue(key) == self.option)
1331 -- Returns the formvalue for this object
1332 function AbstractValue.formvalue(self, section)
1333 return self.map:formvalue(self:cbid(section))
1336 function AbstractValue.additional(self, value)
1337 self.optional = value
1340 function AbstractValue.mandatory(self, value)
1341 self.rmempty = not value
1344 function AbstractValue.parse(self, section, novld)
1345 local fvalue = self:formvalue(section)
1346 local cvalue = self:cfgvalue(section)
1348 -- If favlue and cvalue are both tables and have the same content
1349 -- make them identical
1350 if type(fvalue) == "table" and type(cvalue) == "table" then
1351 local equal = #fvalue == #cvalue
1354 if cvalue[i] ~= fvalue[i] then
1364 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1365 fvalue = self:transform(self:validate(fvalue, section))
1366 if not fvalue and not novld then
1368 self.error[section] = "invalid"
1370 self.error = { [section] = "invalid" }
1372 if self.section.error then
1373 table.insert(self.section.error[section], "invalid")
1375 self.section.error = {[section] = {"invalid"}}
1377 self.map.save = false
1379 if fvalue and not (fvalue == cvalue) then
1380 if self:write(section, fvalue) then
1382 self.section.changed = true
1383 --luci.util.append(self.map.events, self.events)
1386 else -- Unset the UCI or error
1387 if self.rmempty or self.optional then
1388 if self:remove(section) then
1390 self.section.changed = true
1391 --luci.util.append(self.map.events, self.events)
1393 elseif cvalue ~= fvalue and not novld then
1394 self:write(section, fvalue or "")
1396 self.error[section] = "missing"
1398 self.error = { [section] = "missing" }
1400 self.map.save = false
1405 -- Render if this value exists or if it is mandatory
1406 function AbstractValue.render(self, s, scope)
1407 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1410 scope.cbid = self:cbid(s)
1411 scope.striptags = luci.util.striptags
1412 scope.pcdata = luci.util.pcdata
1414 scope.ifattr = function(cond,key,val)
1416 return string.format(
1417 ' %s="%s"', tostring(key),
1418 luci.util.pcdata(tostring( val
1420 or (type(self[key]) ~= "function" and self[key])
1428 scope.attr = function(...)
1429 return scope.ifattr( true, ... )
1432 Node.render(self, scope)
1436 -- Return the UCI value of this object
1437 function AbstractValue.cfgvalue(self, section)
1438 local value = self.map:get(section, self.option)
1441 elseif not self.cast or self.cast == type(value) then
1443 elseif self.cast == "string" then
1444 if type(value) == "table" then
1447 elseif self.cast == "table" then
1448 return luci.util.split(value, "%s+", nil, true)
1452 -- Validate the form value
1453 function AbstractValue.validate(self, value)
1457 AbstractValue.transform = AbstractValue.validate
1461 function AbstractValue.write(self, section, value)
1462 return self.map:set(section, self.option, value)
1466 function AbstractValue.remove(self, section)
1467 return self.map:del(section, self.option)
1474 Value - A one-line value
1475 maxlength: The maximum length
1477 Value = class(AbstractValue)
1479 function Value.__init__(self, ...)
1480 AbstractValue.__init__(self, ...)
1481 self.template = "cbi/value"
1486 function Value.value(self, key, val)
1488 table.insert(self.keylist, tostring(key))
1489 table.insert(self.vallist, tostring(val))
1493 -- DummyValue - This does nothing except being there
1494 DummyValue = class(AbstractValue)
1496 function DummyValue.__init__(self, ...)
1497 AbstractValue.__init__(self, ...)
1498 self.template = "cbi/dvalue"
1502 function DummyValue.cfgvalue(self, section)
1505 if type(self.value) == "function" then
1506 value = self:value(section)
1511 value = AbstractValue.cfgvalue(self, section)
1516 function DummyValue.parse(self)
1522 Flag - A flag being enabled or disabled
1524 Flag = class(AbstractValue)
1526 function Flag.__init__(self, ...)
1527 AbstractValue.__init__(self, ...)
1528 self.template = "cbi/fvalue"
1534 -- A flag can only have two states: set or unset
1535 function Flag.parse(self, section)
1536 local fvalue = self:formvalue(section)
1539 fvalue = self.enabled
1541 fvalue = self.disabled
1544 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1545 if not(fvalue == self:cfgvalue(section)) then
1546 self:write(section, fvalue)
1549 self:remove(section)
1556 ListValue - A one-line value predefined in a list
1557 widget: The widget that will be used (select, radio)
1559 ListValue = class(AbstractValue)
1561 function ListValue.__init__(self, ...)
1562 AbstractValue.__init__(self, ...)
1563 self.template = "cbi/lvalue"
1568 self.widget = "select"
1571 function ListValue.prepare(self, ...)
1572 AbstractValue.prepare(self, ...)
1573 if not self.override_scheme
1574 and self.map:get_scheme(self.section.sectiontype, self.option) then
1575 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1576 if self.value and vs.valuelist and not self.override_values then
1577 for k, v in ipairs(vs.valuelist) do
1579 if not self.override_dependencies
1580 and vs.enum_depends and vs.enum_depends[v.value] then
1581 for i, dep in ipairs(vs.enum_depends[v.value]) do
1582 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1585 self:value(v.value, v.title or v.value, unpack(deps))
1591 function ListValue.value(self, key, val, ...)
1592 if luci.util.contains(self.keylist, key) then
1597 table.insert(self.keylist, tostring(key))
1598 table.insert(self.vallist, tostring(val))
1600 for i, deps in ipairs({...}) do
1601 table.insert(self.deps, {add = "-"..key, deps=deps})
1605 function ListValue.validate(self, val)
1606 if luci.util.contains(self.keylist, val) then
1616 MultiValue - Multiple delimited values
1617 widget: The widget that will be used (select, checkbox)
1618 delimiter: The delimiter that will separate the values (default: " ")
1620 MultiValue = class(AbstractValue)
1622 function MultiValue.__init__(self, ...)
1623 AbstractValue.__init__(self, ...)
1624 self.template = "cbi/mvalue"
1629 self.widget = "checkbox"
1630 self.delimiter = " "
1633 function MultiValue.render(self, ...)
1634 if self.widget == "select" and not self.size then
1635 self.size = #self.vallist
1638 AbstractValue.render(self, ...)
1641 function MultiValue.value(self, key, val)
1642 if luci.util.contains(self.keylist, key) then
1647 table.insert(self.keylist, tostring(key))
1648 table.insert(self.vallist, tostring(val))
1651 function MultiValue.valuelist(self, section)
1652 local val = self:cfgvalue(section)
1654 if not(type(val) == "string") then
1658 return luci.util.split(val, self.delimiter)
1661 function MultiValue.validate(self, val)
1662 val = (type(val) == "table") and val or {val}
1666 for i, value in ipairs(val) do
1667 if luci.util.contains(self.keylist, value) then
1668 result = result and (result .. self.delimiter .. value) or value
1676 StaticList = class(MultiValue)
1678 function StaticList.__init__(self, ...)
1679 MultiValue.__init__(self, ...)
1681 self.valuelist = self.cfgvalue
1683 if not self.override_scheme
1684 and self.map:get_scheme(self.section.sectiontype, self.option) then
1685 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1686 if self.value and vs.values and not self.override_values then
1687 for k, v in pairs(vs.values) do
1694 function StaticList.validate(self, value)
1695 value = (type(value) == "table") and value or {value}
1698 for i, v in ipairs(value) do
1699 if luci.util.contains(self.keylist, v) then
1700 table.insert(valid, v)
1707 DynamicList = class(AbstractValue)
1709 function DynamicList.__init__(self, ...)
1710 AbstractValue.__init__(self, ...)
1711 self.template = "cbi/dynlist"
1717 function DynamicList.value(self, key, val)
1719 table.insert(self.keylist, tostring(key))
1720 table.insert(self.vallist, tostring(val))
1723 function DynamicList.write(self, ...)
1724 self.map.proceed = true
1725 return AbstractValue.write(self, ...)
1728 function DynamicList.formvalue(self, section)
1729 local value = AbstractValue.formvalue(self, section)
1730 value = (type(value) == "table") and value or {value}
1733 for i, v in ipairs(value) do
1735 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1736 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1737 table.insert(valid, v)
1746 TextValue - A multi-line value
1749 TextValue = class(AbstractValue)
1751 function TextValue.__init__(self, ...)
1752 AbstractValue.__init__(self, ...)
1753 self.template = "cbi/tvalue"
1759 Button = class(AbstractValue)
1761 function Button.__init__(self, ...)
1762 AbstractValue.__init__(self, ...)
1763 self.template = "cbi/button"
1764 self.inputstyle = nil
1769 FileUpload = class(AbstractValue)
1771 function FileUpload.__init__(self, ...)
1772 AbstractValue.__init__(self, ...)
1773 self.template = "cbi/upload"
1774 if not self.map.upload_fields then
1775 self.map.upload_fields = { self }
1777 self.map.upload_fields[#self.map.upload_fields+1] = self
1781 function FileUpload.formcreated(self, section)
1782 return AbstractValue.formcreated(self, section) or
1783 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1784 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1787 function FileUpload.cfgvalue(self, section)
1788 local val = AbstractValue.cfgvalue(self, section)
1789 if val and fs.access(val) then
1795 function FileUpload.formvalue(self, section)
1796 local val = AbstractValue.formvalue(self, section)
1798 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1799 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1809 function FileUpload.remove(self, section)
1810 local val = AbstractValue.formvalue(self, section)
1811 if val and fs.access(val) then fs.unlink(val) end
1812 return AbstractValue.remove(self, section)
1816 FileBrowser = class(AbstractValue)
1818 function FileBrowser.__init__(self, ...)
1819 AbstractValue.__init__(self, ...)
1820 self.template = "cbi/browser"