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 fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1334 fvalue = self:transform(self:validate(fvalue, section))
1335 if not fvalue and not novld then
1337 self.error[section] = "invalid"
1339 self.error = { [section] = "invalid" }
1341 if self.section.error then
1342 table.insert(self.section.error[section], "invalid")
1344 self.section.error = {[section] = {"invalid"}}
1346 self.map.save = false
1348 if fvalue and not (fvalue == cvalue) then
1349 if self:write(section, fvalue) then
1351 self.section.changed = true
1352 --luci.util.append(self.map.events, self.events)
1355 else -- Unset the UCI or error
1356 if self.rmempty or self.optional then
1357 if self:remove(section) then
1359 self.section.changed = true
1360 --luci.util.append(self.map.events, self.events)
1362 elseif cvalue ~= fvalue and not novld then
1363 self:write(section, fvalue or "")
1365 self.error[section] = "missing"
1367 self.error = { [section] = "missing" }
1369 self.map.save = false
1374 -- Render if this value exists or if it is mandatory
1375 function AbstractValue.render(self, s, scope)
1376 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1379 scope.cbid = self:cbid(s)
1380 scope.striptags = luci.util.striptags
1381 scope.pcdata = luci.util.pcdata
1383 scope.ifattr = function(cond,key,val)
1385 return string.format(
1386 ' %s="%s"', tostring(key),
1387 luci.util.pcdata(tostring( val
1389 or (type(self[key]) ~= "function" and self[key])
1397 scope.attr = function(...)
1398 return scope.ifattr( true, ... )
1401 Node.render(self, scope)
1405 -- Return the UCI value of this object
1406 function AbstractValue.cfgvalue(self, section)
1407 local value = self.map:get(section, self.option)
1410 elseif not self.cast or self.cast == type(value) then
1412 elseif self.cast == "string" then
1413 if type(value) == "table" then
1416 elseif self.cast == "table" then
1417 return luci.util.split(value, "%s+", nil, true)
1421 -- Validate the form value
1422 function AbstractValue.validate(self, value)
1426 AbstractValue.transform = AbstractValue.validate
1430 function AbstractValue.write(self, section, value)
1431 return self.map:set(section, self.option, value)
1435 function AbstractValue.remove(self, section)
1436 return self.map:del(section, self.option)
1443 Value - A one-line value
1444 maxlength: The maximum length
1446 Value = class(AbstractValue)
1448 function Value.__init__(self, ...)
1449 AbstractValue.__init__(self, ...)
1450 self.template = "cbi/value"
1455 function Value.value(self, key, val)
1457 table.insert(self.keylist, tostring(key))
1458 table.insert(self.vallist, tostring(val))
1462 -- DummyValue - This does nothing except being there
1463 DummyValue = class(AbstractValue)
1465 function DummyValue.__init__(self, ...)
1466 AbstractValue.__init__(self, ...)
1467 self.template = "cbi/dvalue"
1471 function DummyValue.cfgvalue(self, section)
1474 if type(self.value) == "function" then
1475 value = self:value(section)
1480 value = AbstractValue.cfgvalue(self, section)
1485 function DummyValue.parse(self)
1491 Flag - A flag being enabled or disabled
1493 Flag = class(AbstractValue)
1495 function Flag.__init__(self, ...)
1496 AbstractValue.__init__(self, ...)
1497 self.template = "cbi/fvalue"
1503 -- A flag can only have two states: set or unset
1504 function Flag.parse(self, section)
1505 local fvalue = self:formvalue(section)
1508 fvalue = self.enabled
1510 fvalue = self.disabled
1513 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1514 if not(fvalue == self:cfgvalue(section)) then
1515 self:write(section, fvalue)
1518 self:remove(section)
1525 ListValue - A one-line value predefined in a list
1526 widget: The widget that will be used (select, radio)
1528 ListValue = class(AbstractValue)
1530 function ListValue.__init__(self, ...)
1531 AbstractValue.__init__(self, ...)
1532 self.template = "cbi/lvalue"
1537 self.widget = "select"
1540 function ListValue.prepare(self, ...)
1541 AbstractValue.prepare(self, ...)
1542 if not self.override_scheme
1543 and self.map:get_scheme(self.section.sectiontype, self.option) then
1544 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1545 if self.value and vs.valuelist and not self.override_values then
1546 for k, v in ipairs(vs.valuelist) do
1548 if not self.override_dependencies
1549 and vs.enum_depends and vs.enum_depends[v.value] then
1550 for i, dep in ipairs(vs.enum_depends[v.value]) do
1551 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1554 self:value(v.value, v.title or v.value, unpack(deps))
1560 function ListValue.value(self, key, val, ...)
1561 if luci.util.contains(self.keylist, key) then
1566 table.insert(self.keylist, tostring(key))
1567 table.insert(self.vallist, tostring(val))
1569 for i, deps in ipairs({...}) do
1570 table.insert(self.deps, {add = "-"..key, deps=deps})
1574 function ListValue.validate(self, val)
1575 if luci.util.contains(self.keylist, val) then
1585 MultiValue - Multiple delimited values
1586 widget: The widget that will be used (select, checkbox)
1587 delimiter: The delimiter that will separate the values (default: " ")
1589 MultiValue = class(AbstractValue)
1591 function MultiValue.__init__(self, ...)
1592 AbstractValue.__init__(self, ...)
1593 self.template = "cbi/mvalue"
1598 self.widget = "checkbox"
1599 self.delimiter = " "
1602 function MultiValue.render(self, ...)
1603 if self.widget == "select" and not self.size then
1604 self.size = #self.vallist
1607 AbstractValue.render(self, ...)
1610 function MultiValue.value(self, key, val)
1611 if luci.util.contains(self.keylist, key) then
1616 table.insert(self.keylist, tostring(key))
1617 table.insert(self.vallist, tostring(val))
1620 function MultiValue.valuelist(self, section)
1621 local val = self:cfgvalue(section)
1623 if not(type(val) == "string") then
1627 return luci.util.split(val, self.delimiter)
1630 function MultiValue.validate(self, val)
1631 val = (type(val) == "table") and val or {val}
1635 for i, value in ipairs(val) do
1636 if luci.util.contains(self.keylist, value) then
1637 result = result and (result .. self.delimiter .. value) or value
1645 StaticList = class(MultiValue)
1647 function StaticList.__init__(self, ...)
1648 MultiValue.__init__(self, ...)
1650 self.valuelist = self.cfgvalue
1652 if not self.override_scheme
1653 and self.map:get_scheme(self.section.sectiontype, self.option) then
1654 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1655 if self.value and vs.values and not self.override_values then
1656 for k, v in pairs(vs.values) do
1663 function StaticList.validate(self, value)
1664 value = (type(value) == "table") and value or {value}
1667 for i, v in ipairs(value) do
1668 if luci.util.contains(self.keylist, v) then
1669 table.insert(valid, v)
1676 DynamicList = class(AbstractValue)
1678 function DynamicList.__init__(self, ...)
1679 AbstractValue.__init__(self, ...)
1680 self.template = "cbi/dynlist"
1686 function DynamicList.value(self, key, val)
1688 table.insert(self.keylist, tostring(key))
1689 table.insert(self.vallist, tostring(val))
1692 function DynamicList.write(self, ...)
1693 self.map.proceed = true
1694 return AbstractValue.write(self, ...)
1697 function DynamicList.formvalue(self, section)
1698 local value = AbstractValue.formvalue(self, section)
1699 value = (type(value) == "table") and value or {value}
1702 for i, v in ipairs(value) do
1704 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1705 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1706 table.insert(valid, v)
1715 TextValue - A multi-line value
1718 TextValue = class(AbstractValue)
1720 function TextValue.__init__(self, ...)
1721 AbstractValue.__init__(self, ...)
1722 self.template = "cbi/tvalue"
1728 Button = class(AbstractValue)
1730 function Button.__init__(self, ...)
1731 AbstractValue.__init__(self, ...)
1732 self.template = "cbi/button"
1733 self.inputstyle = nil
1738 FileUpload = class(AbstractValue)
1740 function FileUpload.__init__(self, ...)
1741 AbstractValue.__init__(self, ...)
1742 self.template = "cbi/upload"
1743 if not self.map.upload_fields then
1744 self.map.upload_fields = { self }
1746 self.map.upload_fields[#self.map.upload_fields+1] = self
1750 function FileUpload.formcreated(self, section)
1751 return AbstractValue.formcreated(self, section) or
1752 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1753 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1756 function FileUpload.cfgvalue(self, section)
1757 local val = AbstractValue.cfgvalue(self, section)
1758 if val and fs.access(val) then
1764 function FileUpload.formvalue(self, section)
1765 local val = AbstractValue.formvalue(self, section)
1767 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1768 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1778 function FileUpload.remove(self, section)
1779 local val = AbstractValue.formvalue(self, section)
1780 if val and fs.access(val) then fs.unlink(val) end
1781 return AbstractValue.remove(self, section)
1785 FileBrowser = class(AbstractValue)
1787 function FileBrowser.__init__(self, ...)
1788 AbstractValue.__init__(self, ...)
1789 self.template = "cbi/browser"