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")
34 --local event = require "luci.sys.event"
35 local fs = require("nixio.fs")
36 local uci = require("luci.model.uci")
37 local datatypes = require("luci.cbi.datatypes")
38 local class = util.class
39 local instanceof = util.instanceof
51 CREATE_PREFIX = "cbi.cts."
52 REMOVE_PREFIX = "cbi.rts."
53 RESORT_PREFIX = "cbi.sts."
54 FEXIST_PREFIX = "cbi.cbe."
56 -- Loads a CBI map from given file, creating an environment and returns it
57 function load(cbimap, ...)
58 local fs = require "nixio.fs"
59 local i18n = require "luci.i18n"
60 require("luci.config")
63 local upldir = "/lib/uci/upload/"
64 local cbidir = luci.util.libpath() .. "/model/cbi/"
67 if fs.access(cbidir..cbimap..".lua") then
68 func, err = loadfile(cbidir..cbimap..".lua")
69 elseif fs.access(cbimap) then
70 func, err = loadfile(cbimap)
72 func, err = nil, "Model '" .. cbimap .. "' not found!"
77 luci.i18n.loadc("base")
80 translate=i18n.translate,
81 translatef=i18n.translatef,
85 setfenv(func, setmetatable(env, {__index =
87 return rawget(tbl, key) or _M[key] or _G[key]
90 local maps = { func() }
92 local has_upload = false
94 for i, map in ipairs(maps) do
95 if not instanceof(map, Node) then
96 error("CBI map returns no valid map object!")
100 if map.upload_fields then
102 for _, field in ipairs(map.upload_fields) do
104 field.config .. '.' ..
105 field.section.sectiontype .. '.' ..
114 local uci = luci.model.uci.cursor()
115 local prm = luci.http.context.request.message.params
118 luci.http.setfilehandler(
119 function( field, chunk, eof )
120 if not field then return end
121 if field.name and not cbid then
122 local c, s, o = field.name:gmatch(
123 "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
126 if c and s and o then
127 local t = uci:get( c, s )
128 if t and uploads[c.."."..t.."."..o] then
129 local path = upldir .. field.name
130 fd = io.open(path, "w")
139 if field.name == cbid and fd then
156 -- Node pseudo abstract class
159 function Node.__init__(self, title, description)
161 self.title = title or ""
162 self.description = description or ""
163 self.template = "cbi/node"
167 function Node._run_hook(self, hook)
168 if type(self[hook]) == "function" then
169 return self[hook](self)
173 function Node._run_hooks(self, ...)
176 for _, f in ipairs(arg) do
177 if type(self[f]) == "function" then
186 function Node.prepare(self, ...)
187 for k, child in ipairs(self.children) do
192 -- Append child nodes
193 function Node.append(self, obj)
194 table.insert(self.children, obj)
197 -- Parse this node and its children
198 function Node.parse(self, ...)
199 for k, child in ipairs(self.children) do
205 function Node.render(self, scope)
209 luci.template.render(self.template, scope)
212 -- Render the children
213 function Node.render_children(self, ...)
214 for k, node in ipairs(self.children) do
221 A simple template element
223 Template = class(Node)
225 function Template.__init__(self, template)
227 self.template = template
230 function Template.render(self)
231 luci.template.render(self.template, {self=self})
234 function Template.parse(self, readinput)
235 self.readinput = (readinput ~= false)
236 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
241 Map - A map describing a configuration file
245 function Map.__init__(self, config, ...)
246 Node.__init__(self, ...)
249 self.parsechain = {self.config}
250 self.template = "cbi/map"
251 self.apply_on_parse = nil
252 self.readinput = true
256 self.uci = uci.cursor()
261 if not self.uci:load(self.config) then
262 error("Unable to read UCI data: " .. self.config)
266 function Map.formvalue(self, key)
267 return self.readinput and luci.http.formvalue(key)
270 function Map.formvaluetable(self, key)
271 return self.readinput and luci.http.formvaluetable(key) or {}
274 function Map.get_scheme(self, sectiontype, option)
276 return self.scheme and self.scheme.sections[sectiontype]
278 return self.scheme and self.scheme.variables[sectiontype]
279 and self.scheme.variables[sectiontype][option]
283 function Map.submitstate(self)
284 return self:formvalue("cbi.submit")
287 -- Chain foreign config
288 function Map.chain(self, config)
289 table.insert(self.parsechain, config)
292 function Map.state_handler(self, state)
296 -- Use optimized UCI writing
297 function Map.parse(self, readinput, ...)
298 self.readinput = (readinput ~= false)
299 self:_run_hooks("on_parse")
301 if self:formvalue("cbi.skip") then
302 self.state = FORM_SKIP
303 return self:state_handler(self.state)
306 Node.parse(self, ...)
309 self:_run_hooks("on_save", "on_before_save")
310 for i, config in ipairs(self.parsechain) do
311 self.uci:save(config)
313 self:_run_hooks("on_after_save")
314 if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
315 self:_run_hooks("on_before_commit")
316 for i, config in ipairs(self.parsechain) do
317 self.uci:commit(config)
319 -- Refresh data because commit changes section names
320 self.uci:load(config)
322 self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
323 if self.apply_on_parse then
324 self.uci:apply(self.parsechain)
325 self:_run_hooks("on_apply", "on_after_apply")
327 -- This is evaluated by the dispatcher and delegated to the
328 -- template which in turn fires XHR to perform the actual
330 self.apply_needed = true
334 Node.parse(self, true)
337 for i, config in ipairs(self.parsechain) do
338 self.uci:unload(config)
340 if type(self.commit_handler) == "function" then
341 self:commit_handler(self:submitstate())
345 if self:submitstate() then
346 if not self.save then
347 self.state = FORM_INVALID
348 elseif self.proceed then
349 self.state = FORM_PROCEED
351 self.state = self.changed and FORM_CHANGED or FORM_VALID
354 self.state = FORM_NODATA
357 return self:state_handler(self.state)
360 function Map.render(self, ...)
361 self:_run_hooks("on_init")
362 Node.render(self, ...)
365 -- Creates a child section
366 function Map.section(self, class, ...)
367 if instanceof(class, AbstractSection) then
368 local obj = class(self, ...)
372 error("class must be a descendent of AbstractSection")
377 function Map.add(self, sectiontype)
378 return self.uci:add(self.config, sectiontype)
382 function Map.set(self, section, option, value)
383 if type(value) ~= "table" or #value > 0 then
385 return self.uci:set(self.config, section, option, value)
387 return self.uci:set(self.config, section, value)
390 return Map.del(self, section, option)
395 function Map.del(self, section, option)
397 return self.uci:delete(self.config, section, option)
399 return self.uci:delete(self.config, section)
404 function Map.get(self, section, option)
406 return self.uci:get_all(self.config)
408 return self.uci:get(self.config, section, option)
410 return self.uci:get_all(self.config, section)
417 Compound = class(Node)
419 function Compound.__init__(self, ...)
421 self.template = "cbi/compound"
422 self.children = {...}
425 function Compound.populate_delegator(self, delegator)
426 for _, v in ipairs(self.children) do
427 v.delegator = delegator
431 function Compound.parse(self, ...)
432 local cstate, state = 0
434 for k, child in ipairs(self.children) do
435 cstate = child:parse(...)
436 state = (not state or cstate < state) and cstate or state
444 Delegator - Node controller
446 Delegator = class(Node)
447 function Delegator.__init__(self, ...)
448 Node.__init__(self, ...)
450 self.defaultpath = {}
451 self.pageaction = false
452 self.readinput = true
453 self.allow_reset = false
454 self.allow_cancel = false
455 self.allow_back = false
456 self.allow_finish = false
457 self.template = "cbi/delegator"
460 function Delegator.set(self, name, node)
461 assert(not self.nodes[name], "Duplicate entry")
463 self.nodes[name] = node
466 function Delegator.add(self, name, node)
467 node = self:set(name, node)
468 self.defaultpath[#self.defaultpath+1] = name
471 function Delegator.insert_after(self, name, after)
472 local n = #self.chain + 1
473 for k, v in ipairs(self.chain) do
479 table.insert(self.chain, n, name)
482 function Delegator.set_route(self, ...)
483 local n, chain, route = 0, self.chain, {...}
485 if chain[i] == self.current then
494 for i = n + 1, #chain do
499 function Delegator.get(self, name)
500 local node = self.nodes[name]
502 if type(node) == "string" then
503 node = load(node, name)
506 if type(node) == "table" and getmetatable(node) == nil then
507 node = Compound(unpack(node))
513 function Delegator.parse(self, ...)
514 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
515 if self:_run_hooks("on_cancel") then
520 if not Map.formvalue(self, "cbi.delg.current") then
521 self:_run_hooks("on_init")
525 self.chain = self.chain or self:get_chain()
526 self.current = self.current or self:get_active()
527 self.active = self.active or self:get(self.current)
528 assert(self.active, "Invalid state")
530 local stat = FORM_DONE
531 if type(self.active) ~= "function" then
532 self.active:populate_delegator(self)
533 stat = self.active:parse()
538 if stat > FORM_PROCEED then
539 if Map.formvalue(self, "cbi.delg.back") then
540 newcurrent = self:get_prev(self.current)
542 newcurrent = self:get_next(self.current)
544 elseif stat < FORM_PROCEED then
549 if not Map.formvalue(self, "cbi.submit") then
551 elseif stat > FORM_PROCEED
552 and (not newcurrent or not self:get(newcurrent)) then
553 return self:_run_hook("on_done") or FORM_DONE
555 self.current = newcurrent or self.current
556 self.active = self:get(self.current)
557 if type(self.active) ~= "function" then
558 self.active:populate_delegator(self)
559 local stat = self.active:parse(false)
560 if stat == FORM_SKIP then
561 return self:parse(...)
566 return self:parse(...)
571 function Delegator.get_next(self, state)
572 for k, v in ipairs(self.chain) do
574 return self.chain[k+1]
579 function Delegator.get_prev(self, state)
580 for k, v in ipairs(self.chain) do
582 return self.chain[k-1]
587 function Delegator.get_chain(self)
588 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
589 return type(x) == "table" and x or {x}
592 function Delegator.get_active(self)
593 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
601 Page.__init__ = Node.__init__
602 Page.parse = function() end
606 SimpleForm - A Simple non-UCI form
608 SimpleForm = class(Node)
610 function SimpleForm.__init__(self, config, title, description, data)
611 Node.__init__(self, title, description)
613 self.data = data or {}
614 self.template = "cbi/simpleform"
616 self.pageaction = false
617 self.readinput = true
620 SimpleForm.formvalue = Map.formvalue
621 SimpleForm.formvaluetable = Map.formvaluetable
623 function SimpleForm.parse(self, readinput, ...)
624 self.readinput = (readinput ~= false)
626 if self:formvalue("cbi.skip") then
630 if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
634 if self:submitstate() then
635 Node.parse(self, 1, ...)
639 for k, j in ipairs(self.children) do
640 for i, v in ipairs(j.children) do
642 and (not v.tag_missing or not v.tag_missing[1])
643 and (not v.tag_invalid or not v.tag_invalid[1])
649 not self:submitstate() and FORM_NODATA
650 or valid and FORM_VALID
653 self.dorender = not self.handle
655 local nrender, nstate = self:handle(state, self.data)
656 self.dorender = self.dorender or (nrender ~= false)
657 state = nstate or state
662 function SimpleForm.render(self, ...)
663 if self.dorender then
664 Node.render(self, ...)
668 function SimpleForm.submitstate(self)
669 return self:formvalue("cbi.submit")
672 function SimpleForm.section(self, class, ...)
673 if instanceof(class, AbstractSection) then
674 local obj = class(self, ...)
678 error("class must be a descendent of AbstractSection")
682 -- Creates a child field
683 function SimpleForm.field(self, class, ...)
685 for k, v in ipairs(self.children) do
686 if instanceof(v, SimpleSection) then
692 section = self:section(SimpleSection)
695 if instanceof(class, AbstractValue) then
696 local obj = class(self, section, ...)
697 obj.track_missing = true
701 error("class must be a descendent of AbstractValue")
705 function SimpleForm.set(self, section, option, value)
706 self.data[option] = value
710 function SimpleForm.del(self, section, option)
711 self.data[option] = nil
715 function SimpleForm.get(self, section, option)
716 return self.data[option]
720 function SimpleForm.get_scheme()
725 Form = class(SimpleForm)
727 function Form.__init__(self, ...)
728 SimpleForm.__init__(self, ...)
736 AbstractSection = class(Node)
738 function AbstractSection.__init__(self, map, sectiontype, ...)
739 Node.__init__(self, ...)
740 self.sectiontype = sectiontype
742 self.config = map.config
747 self.tag_invalid = {}
748 self.tag_deperror = {}
752 self.addremove = false
756 -- Define a tab for the section
757 function AbstractSection.tab(self, tab, title, desc)
758 self.tabs = self.tabs or { }
759 self.tab_names = self.tab_names or { }
761 self.tab_names[#self.tab_names+1] = tab
769 -- Check whether the section has tabs
770 function AbstractSection.has_tabs(self)
771 return (self.tabs ~= nil) and (next(self.tabs) ~= nil)
774 -- Appends a new option
775 function AbstractSection.option(self, class, option, ...)
776 if instanceof(class, AbstractValue) then
777 local obj = class(self.map, self, option, ...)
779 self.fields[option] = obj
781 elseif class == true then
782 error("No valid class was given and autodetection failed.")
784 error("class must be a descendant of AbstractValue")
788 -- Appends a new tabbed option
789 function AbstractSection.taboption(self, tab, ...)
791 assert(tab and self.tabs and self.tabs[tab],
792 "Cannot assign option to not existing tab %q" % tostring(tab))
794 local l = self.tabs[tab].childs
795 local o = AbstractSection.option(self, ...)
797 if o then l[#l+1] = o end
802 -- Render a single tab
803 function AbstractSection.render_tab(self, tab, ...)
805 assert(tab and self.tabs and self.tabs[tab],
806 "Cannot render not existing tab %q" % tostring(tab))
808 for _, node in ipairs(self.tabs[tab].childs) do
813 -- Parse optional options
814 function AbstractSection.parse_optionals(self, section)
815 if not self.optional then
819 self.optionals[section] = {}
821 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
822 for k,v in ipairs(self.children) do
823 if v.optional and not v:cfgvalue(section) and not self:has_tabs() then
824 if field == v.option then
826 self.map.proceed = true
828 table.insert(self.optionals[section], v)
833 if field and #field > 0 and self.dynamic then
834 self:add_dynamic(field)
838 -- Add a dynamic option
839 function AbstractSection.add_dynamic(self, field, optional)
840 local o = self:option(Value, field, field)
841 o.optional = optional
844 -- Parse all dynamic options
845 function AbstractSection.parse_dynamic(self, section)
846 if not self.dynamic then
850 local arr = luci.util.clone(self:cfgvalue(section))
851 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
852 for k, v in pairs(form) do
856 for key,val in pairs(arr) do
859 for i,c in ipairs(self.children) do
860 if c.option == key then
865 if create and key:sub(1, 1) ~= "." then
866 self.map.proceed = true
867 self:add_dynamic(key, true)
872 -- Returns the section's UCI table
873 function AbstractSection.cfgvalue(self, section)
874 return self.map:get(section)
878 function AbstractSection.push_events(self)
879 --luci.util.append(self.map.events, self.events)
880 self.map.changed = true
883 -- Removes the section
884 function AbstractSection.remove(self, section)
885 self.map.proceed = true
886 return self.map:del(section)
889 -- Creates the section
890 function AbstractSection.create(self, section)
894 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
896 section = self.map:add(self.sectiontype)
901 for k,v in pairs(self.children) do
903 self.map:set(section, v.option, v.default)
907 for k,v in pairs(self.defaults) do
908 self.map:set(section, k, v)
912 self.map.proceed = true
918 SimpleSection = class(AbstractSection)
920 function SimpleSection.__init__(self, form, ...)
921 AbstractSection.__init__(self, form, nil, ...)
922 self.template = "cbi/nullsection"
926 Table = class(AbstractSection)
928 function Table.__init__(self, form, data, ...)
929 local datasource = {}
931 datasource.config = "table"
932 self.data = data or {}
934 datasource.formvalue = Map.formvalue
935 datasource.formvaluetable = Map.formvaluetable
936 datasource.readinput = true
938 function datasource.get(self, section, option)
939 return tself.data[section] and tself.data[section][option]
942 function datasource.submitstate(self)
943 return Map.formvalue(self, "cbi.submit")
946 function datasource.del(...)
950 function datasource.get_scheme()
954 AbstractSection.__init__(self, datasource, "table", ...)
955 self.template = "cbi/tblsection"
956 self.rowcolors = true
957 self.anonymous = true
960 function Table.parse(self, readinput)
961 self.map.readinput = (readinput ~= false)
962 for i, k in ipairs(self:cfgsections()) do
963 if self.map:submitstate() then
969 function Table.cfgsections(self)
972 for i, v in luci.util.kspairs(self.data) do
973 table.insert(sections, i)
979 function Table.update(self, data)
986 NamedSection - A fixed configuration section defined by its name
988 NamedSection = class(AbstractSection)
990 function NamedSection.__init__(self, map, section, stype, ...)
991 AbstractSection.__init__(self, map, stype, ...)
994 self.addremove = false
995 self.template = "cbi/nsection"
996 self.section = section
999 function NamedSection.parse(self, novld)
1000 local s = self.section
1001 local active = self:cfgvalue(s)
1003 if self.addremove then
1004 local path = self.config.."."..s
1005 if active then -- Remove the section
1006 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1010 else -- Create and apply default values
1011 if self.map:formvalue("cbi.cns."..path) then
1019 AbstractSection.parse_dynamic(self, s)
1020 if self.map:submitstate() then
1023 AbstractSection.parse_optionals(self, s)
1025 if self.changed then
1033 TypedSection - A (set of) configuration section(s) defined by the type
1034 addremove: Defines whether the user can add/remove sections of this type
1035 anonymous: Allow creating anonymous sections
1036 validate: a validation function returning nil if the section is invalid
1038 TypedSection = class(AbstractSection)
1040 function TypedSection.__init__(self, map, type, ...)
1041 AbstractSection.__init__(self, map, type, ...)
1043 self.template = "cbi/tsection"
1045 self.anonymous = false
1048 -- Return all matching UCI sections for this TypedSection
1049 function TypedSection.cfgsections(self)
1051 self.map.uci:foreach(self.map.config, self.sectiontype,
1053 if self:checkscope(section[".name"]) then
1054 table.insert(sections, section[".name"])
1061 -- Limits scope to sections that have certain option => value pairs
1062 function TypedSection.depends(self, option, value)
1063 table.insert(self.deps, {option=option, value=value})
1066 function TypedSection.parse(self, novld)
1067 if self.addremove then
1069 local crval = REMOVE_PREFIX .. self.config
1070 local name = self.map:formvaluetable(crval)
1071 for k,v in pairs(name) do
1072 if k:sub(-2) == ".x" then
1073 k = k:sub(1, #k - 2)
1075 if self:cfgvalue(k) and self:checkscope(k) then
1082 for i, k in ipairs(self:cfgsections()) do
1083 AbstractSection.parse_dynamic(self, k)
1084 if self.map:submitstate() then
1085 Node.parse(self, k, novld)
1087 AbstractSection.parse_optionals(self, k)
1090 if self.addremove then
1093 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1094 local name = self.map:formvalue(crval)
1095 if self.anonymous then
1097 created = self:create()
1101 -- Ignore if it already exists
1102 if self:cfgvalue(name) then
1106 name = self:checkscope(name)
1109 self.err_invalid = true
1112 if name and #name > 0 then
1113 created = self:create(name) and name
1115 self.invalid_cts = true
1122 AbstractSection.parse_optionals(self, created)
1126 if self.sortable then
1127 local stval = RESORT_PREFIX .. self.config .. "." .. self.sectiontype
1128 local order = self.map:formvalue(stval)
1129 if order and #order > 0 then
1132 for sid in util.imatch(order) do
1133 self.map.uci:reorder(self.config, sid, num)
1136 self.changed = (num > 0)
1140 if created or self.changed then
1145 -- Verifies scope of sections
1146 function TypedSection.checkscope(self, section)
1147 -- Check if we are not excluded
1148 if self.filter and not self:filter(section) then
1152 -- Check if at least one dependency is met
1153 if #self.deps > 0 and self:cfgvalue(section) then
1156 for k, v in ipairs(self.deps) do
1157 if self:cfgvalue(section)[v.option] == v.value then
1167 return self:validate(section)
1171 -- Dummy validate function
1172 function TypedSection.validate(self, section)
1178 AbstractValue - An abstract Value Type
1179 null: Value can be empty
1180 valid: A function returning the value if it is valid otherwise nil
1181 depends: A table of option => value pairs of which one must be true
1182 default: The default value
1183 size: The size of the input fields
1184 rmempty: Unset value if empty
1185 optional: This value is optional (see AbstractSection.optionals)
1187 AbstractValue = class(Node)
1189 function AbstractValue.__init__(self, map, section, option, ...)
1190 Node.__init__(self, ...)
1191 self.section = section
1192 self.option = option
1194 self.config = map.config
1195 self.tag_invalid = {}
1196 self.tag_missing = {}
1197 self.tag_reqerror = {}
1201 --self.cast = "string"
1203 self.track_missing = false
1207 self.optional = false
1210 function AbstractValue.prepare(self)
1211 self.cast = self.cast or "string"
1214 -- Add a dependencie to another section field
1215 function AbstractValue.depends(self, field, value)
1217 if type(field) == "string" then
1224 table.insert(self.deps, {deps=deps, add=""})
1227 -- Generates the unique CBID
1228 function AbstractValue.cbid(self, section)
1229 return "cbid."..self.map.config.."."..section.."."..self.option
1232 -- Return whether this object should be created
1233 function AbstractValue.formcreated(self, section)
1234 local key = "cbi.opt."..self.config.."."..section
1235 return (self.map:formvalue(key) == self.option)
1238 -- Returns the formvalue for this object
1239 function AbstractValue.formvalue(self, section)
1240 return self.map:formvalue(self:cbid(section))
1243 function AbstractValue.additional(self, value)
1244 self.optional = value
1247 function AbstractValue.mandatory(self, value)
1248 self.rmempty = not value
1251 function AbstractValue.add_error(self, section, type, msg)
1252 self.error = self.error or { }
1253 self.error[section] = msg or type
1255 self.section.error = self.section.error or { }
1256 self.section.error[section] = self.section.error[section] or { }
1257 table.insert(self.section.error[section], msg or type)
1259 if type == "invalid" then
1260 self.tag_invalid[section] = true
1261 elseif type == "missing" then
1262 self.tag_missing[section] = true
1265 self.tag_error[section] = true
1266 self.map.save = false
1269 function AbstractValue.parse(self, section, novld)
1270 local fvalue = self:formvalue(section)
1271 local cvalue = self:cfgvalue(section)
1273 -- If favlue and cvalue are both tables and have the same content
1274 -- make them identical
1275 if type(fvalue) == "table" and type(cvalue) == "table" then
1276 local equal = #fvalue == #cvalue
1279 if cvalue[i] ~= fvalue[i] then
1289 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1291 fvalue, val_err = self:validate(fvalue, section)
1292 fvalue = self:transform(fvalue)
1294 if not fvalue and not novld then
1295 self:add_error(section, "invalid", val_err)
1298 if fvalue and (self.forcewrite or not (fvalue == cvalue)) then
1299 if self:write(section, fvalue) then
1301 self.section.changed = true
1302 --luci.util.append(self.map.events, self.events)
1305 else -- Unset the UCI or error
1306 if self.rmempty or self.optional then
1307 if self:remove(section) then
1309 self.section.changed = true
1310 --luci.util.append(self.map.events, self.events)
1312 elseif cvalue ~= fvalue and not novld then
1313 -- trigger validator with nil value to get custom user error msg.
1314 local _, val_err = self:validate(nil, section)
1315 self:add_error(section, "missing", val_err)
1320 -- Render if this value exists or if it is mandatory
1321 function AbstractValue.render(self, s, scope)
1322 if not self.optional or self.section:has_tabs() or self:cfgvalue(s) or self:formcreated(s) then
1325 scope.cbid = self:cbid(s)
1326 scope.striptags = luci.util.striptags
1327 scope.pcdata = luci.util.pcdata
1329 scope.ifattr = function(cond,key,val)
1331 return string.format(
1332 ' %s="%s"', tostring(key),
1333 luci.util.pcdata(tostring( val
1335 or (type(self[key]) ~= "function" and self[key])
1343 scope.attr = function(...)
1344 return scope.ifattr( true, ... )
1347 Node.render(self, scope)
1351 -- Return the UCI value of this object
1352 function AbstractValue.cfgvalue(self, section)
1354 if self.tag_error[section] then
1355 value = self:formvalue(section)
1357 value = self.map:get(section, self.option)
1362 elseif not self.cast or self.cast == type(value) then
1364 elseif self.cast == "string" then
1365 if type(value) == "table" then
1368 elseif self.cast == "table" then
1373 -- Validate the form value
1374 function AbstractValue.validate(self, value)
1375 if self.datatype and value then
1377 local dt, ar = self.datatype:match("^(%w+)%(([^%(%)]+)%)")
1381 for a in ar:gmatch("[^%s,]+") do
1388 if dt and datatypes[dt] then
1389 if type(value) == "table" then
1391 for _, v in ipairs(value) do
1392 if v and #v > 0 and not datatypes[dt](v, unpack(args)) then
1397 if not datatypes[dt](value, unpack(args)) then
1407 AbstractValue.transform = AbstractValue.validate
1411 function AbstractValue.write(self, section, value)
1412 return self.map:set(section, self.option, value)
1416 function AbstractValue.remove(self, section)
1417 return self.map:del(section, self.option)
1424 Value - A one-line value
1425 maxlength: The maximum length
1427 Value = class(AbstractValue)
1429 function Value.__init__(self, ...)
1430 AbstractValue.__init__(self, ...)
1431 self.template = "cbi/value"
1436 function Value.reset_values(self)
1441 function Value.value(self, key, val)
1443 table.insert(self.keylist, tostring(key))
1444 table.insert(self.vallist, tostring(val))
1448 -- DummyValue - This does nothing except being there
1449 DummyValue = class(AbstractValue)
1451 function DummyValue.__init__(self, ...)
1452 AbstractValue.__init__(self, ...)
1453 self.template = "cbi/dvalue"
1457 function DummyValue.cfgvalue(self, section)
1460 if type(self.value) == "function" then
1461 value = self:value(section)
1466 value = AbstractValue.cfgvalue(self, section)
1471 function DummyValue.parse(self)
1477 Flag - A flag being enabled or disabled
1479 Flag = class(AbstractValue)
1481 function Flag.__init__(self, ...)
1482 AbstractValue.__init__(self, ...)
1483 self.template = "cbi/fvalue"
1487 self.default = self.disabled
1490 -- A flag can only have two states: set or unset
1491 function Flag.parse(self, section)
1492 local fexists = self.map:formvalue(
1493 FEXIST_PREFIX .. self.config .. "." .. section .. "." .. self.option)
1496 local fvalue = self:formvalue(section) and self.enabled or self.disabled
1497 if fvalue ~= self.default or (not self.optional and not self.rmempty) then
1498 self:write(section, fvalue)
1500 self:remove(section)
1503 self:remove(section)
1507 function Flag.cfgvalue(self, section)
1508 return AbstractValue.cfgvalue(self, section) or self.default
1513 ListValue - A one-line value predefined in a list
1514 widget: The widget that will be used (select, radio)
1516 ListValue = class(AbstractValue)
1518 function ListValue.__init__(self, ...)
1519 AbstractValue.__init__(self, ...)
1520 self.template = "cbi/lvalue"
1525 self.widget = "select"
1528 function ListValue.reset_values(self)
1533 function ListValue.value(self, key, val, ...)
1534 if luci.util.contains(self.keylist, key) then
1539 table.insert(self.keylist, tostring(key))
1540 table.insert(self.vallist, tostring(val))
1542 for i, deps in ipairs({...}) do
1543 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1547 function ListValue.validate(self, val)
1548 if luci.util.contains(self.keylist, val) then
1558 MultiValue - Multiple delimited values
1559 widget: The widget that will be used (select, checkbox)
1560 delimiter: The delimiter that will separate the values (default: " ")
1562 MultiValue = class(AbstractValue)
1564 function MultiValue.__init__(self, ...)
1565 AbstractValue.__init__(self, ...)
1566 self.template = "cbi/mvalue"
1571 self.widget = "checkbox"
1572 self.delimiter = " "
1575 function MultiValue.render(self, ...)
1576 if self.widget == "select" and not self.size then
1577 self.size = #self.vallist
1580 AbstractValue.render(self, ...)
1583 function MultiValue.reset_values(self)
1588 function MultiValue.value(self, key, val)
1589 if luci.util.contains(self.keylist, key) then
1594 table.insert(self.keylist, tostring(key))
1595 table.insert(self.vallist, tostring(val))
1598 function MultiValue.valuelist(self, section)
1599 local val = self:cfgvalue(section)
1601 if not(type(val) == "string") then
1605 return luci.util.split(val, self.delimiter)
1608 function MultiValue.validate(self, val)
1609 val = (type(val) == "table") and val or {val}
1613 for i, value in ipairs(val) do
1614 if luci.util.contains(self.keylist, value) then
1615 result = result and (result .. self.delimiter .. value) or value
1623 StaticList = class(MultiValue)
1625 function StaticList.__init__(self, ...)
1626 MultiValue.__init__(self, ...)
1628 self.valuelist = self.cfgvalue
1630 if not self.override_scheme
1631 and self.map:get_scheme(self.section.sectiontype, self.option) then
1632 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1633 if self.value and vs.values and not self.override_values then
1634 for k, v in pairs(vs.values) do
1641 function StaticList.validate(self, value)
1642 value = (type(value) == "table") and value or {value}
1645 for i, v in ipairs(value) do
1646 if luci.util.contains(self.keylist, v) then
1647 table.insert(valid, v)
1654 DynamicList = class(AbstractValue)
1656 function DynamicList.__init__(self, ...)
1657 AbstractValue.__init__(self, ...)
1658 self.template = "cbi/dynlist"
1664 function DynamicList.reset_values(self)
1669 function DynamicList.value(self, key, val)
1671 table.insert(self.keylist, tostring(key))
1672 table.insert(self.vallist, tostring(val))
1675 function DynamicList.write(self, section, value)
1678 if type(value) == "table" then
1680 for _, x in ipairs(value) do
1681 if x and #x > 0 then
1689 if self.cast == "string" then
1690 value = table.concat(t, " ")
1695 return AbstractValue.write(self, section, value)
1698 function DynamicList.cfgvalue(self, section)
1699 local value = AbstractValue.cfgvalue(self, section)
1701 if type(value) == "string" then
1704 for x in value:gmatch("%S+") do
1715 function DynamicList.formvalue(self, section)
1716 local value = AbstractValue.formvalue(self, section)
1718 if type(value) == "string" then
1719 if self.cast == "string" then
1722 for x in value:gmatch("%S+") do
1736 TextValue - A multi-line value
1739 TextValue = class(AbstractValue)
1741 function TextValue.__init__(self, ...)
1742 AbstractValue.__init__(self, ...)
1743 self.template = "cbi/tvalue"
1749 Button = class(AbstractValue)
1751 function Button.__init__(self, ...)
1752 AbstractValue.__init__(self, ...)
1753 self.template = "cbi/button"
1754 self.inputstyle = nil
1759 FileUpload = class(AbstractValue)
1761 function FileUpload.__init__(self, ...)
1762 AbstractValue.__init__(self, ...)
1763 self.template = "cbi/upload"
1764 if not self.map.upload_fields then
1765 self.map.upload_fields = { self }
1767 self.map.upload_fields[#self.map.upload_fields+1] = self
1771 function FileUpload.formcreated(self, section)
1772 return AbstractValue.formcreated(self, section) or
1773 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1774 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1777 function FileUpload.cfgvalue(self, section)
1778 local val = AbstractValue.cfgvalue(self, section)
1779 if val and fs.access(val) then
1785 function FileUpload.formvalue(self, section)
1786 local val = AbstractValue.formvalue(self, section)
1788 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1789 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1799 function FileUpload.remove(self, section)
1800 local val = AbstractValue.formvalue(self, section)
1801 if val and fs.access(val) then fs.unlink(val) end
1802 return AbstractValue.remove(self, section)
1806 FileBrowser = class(AbstractValue)
1808 function FileBrowser.__init__(self, ...)
1809 AbstractValue.__init__(self, ...)
1810 self.template = "cbi/browser"