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, ...)
215 for k, node in ipairs(self.children) do
216 node.last_child = (k == #self.children)
223 A simple template element
225 Template = class(Node)
227 function Template.__init__(self, template)
229 self.template = template
232 function Template.render(self)
233 luci.template.render(self.template, {self=self})
236 function Template.parse(self, readinput)
237 self.readinput = (readinput ~= false)
238 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
243 Map - A map describing a configuration file
247 function Map.__init__(self, config, ...)
248 Node.__init__(self, ...)
251 self.parsechain = {self.config}
252 self.template = "cbi/map"
253 self.apply_on_parse = nil
254 self.readinput = true
258 self.uci = uci.cursor()
263 if not self.uci:load(self.config) then
264 error("Unable to read UCI data: " .. self.config)
268 function Map.formvalue(self, key)
269 return self.readinput and luci.http.formvalue(key)
272 function Map.formvaluetable(self, key)
273 return self.readinput and luci.http.formvaluetable(key) or {}
276 function Map.get_scheme(self, sectiontype, option)
278 return self.scheme and self.scheme.sections[sectiontype]
280 return self.scheme and self.scheme.variables[sectiontype]
281 and self.scheme.variables[sectiontype][option]
285 function Map.submitstate(self)
286 return self:formvalue("cbi.submit")
289 -- Chain foreign config
290 function Map.chain(self, config)
291 table.insert(self.parsechain, config)
294 function Map.state_handler(self, state)
298 -- Use optimized UCI writing
299 function Map.parse(self, readinput, ...)
300 self.readinput = (readinput ~= false)
301 self:_run_hooks("on_parse")
303 if self:formvalue("cbi.skip") then
304 self.state = FORM_SKIP
305 return self:state_handler(self.state)
308 Node.parse(self, ...)
311 self:_run_hooks("on_save", "on_before_save")
312 for i, config in ipairs(self.parsechain) do
313 self.uci:save(config)
315 self:_run_hooks("on_after_save")
316 if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
317 self:_run_hooks("on_before_commit")
318 for i, config in ipairs(self.parsechain) do
319 self.uci:commit(config)
321 -- Refresh data because commit changes section names
322 self.uci:load(config)
324 self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
325 if self.apply_on_parse then
326 self.uci:apply(self.parsechain)
327 self:_run_hooks("on_apply", "on_after_apply")
329 -- This is evaluated by the dispatcher and delegated to the
330 -- template which in turn fires XHR to perform the actual
332 self.apply_needed = true
336 Node.parse(self, true)
339 for i, config in ipairs(self.parsechain) do
340 self.uci:unload(config)
342 if type(self.commit_handler) == "function" then
343 self:commit_handler(self:submitstate())
347 if self:submitstate() then
348 if not self.save then
349 self.state = FORM_INVALID
350 elseif self.proceed then
351 self.state = FORM_PROCEED
353 self.state = self.changed and FORM_CHANGED or FORM_VALID
356 self.state = FORM_NODATA
359 return self:state_handler(self.state)
362 function Map.render(self, ...)
363 self:_run_hooks("on_init")
364 Node.render(self, ...)
367 -- Creates a child section
368 function Map.section(self, class, ...)
369 if instanceof(class, AbstractSection) then
370 local obj = class(self, ...)
374 error("class must be a descendent of AbstractSection")
379 function Map.add(self, sectiontype)
380 return self.uci:add(self.config, sectiontype)
384 function Map.set(self, section, option, value)
385 if type(value) ~= "table" or #value > 0 then
387 return self.uci:set(self.config, section, option, value)
389 return self.uci:set(self.config, section, value)
392 return Map.del(self, section, option)
397 function Map.del(self, section, option)
399 return self.uci:delete(self.config, section, option)
401 return self.uci:delete(self.config, section)
406 function Map.get(self, section, option)
408 return self.uci:get_all(self.config)
410 return self.uci:get(self.config, section, option)
412 return self.uci:get_all(self.config, section)
419 Compound = class(Node)
421 function Compound.__init__(self, ...)
423 self.template = "cbi/compound"
424 self.children = {...}
427 function Compound.populate_delegator(self, delegator)
428 for _, v in ipairs(self.children) do
429 v.delegator = delegator
433 function Compound.parse(self, ...)
434 local cstate, state = 0
436 for k, child in ipairs(self.children) do
437 cstate = child:parse(...)
438 state = (not state or cstate < state) and cstate or state
446 Delegator - Node controller
448 Delegator = class(Node)
449 function Delegator.__init__(self, ...)
450 Node.__init__(self, ...)
452 self.defaultpath = {}
453 self.pageaction = false
454 self.readinput = true
455 self.allow_reset = false
456 self.allow_cancel = false
457 self.allow_back = false
458 self.allow_finish = false
459 self.template = "cbi/delegator"
462 function Delegator.set(self, name, node)
463 assert(not self.nodes[name], "Duplicate entry")
465 self.nodes[name] = node
468 function Delegator.add(self, name, node)
469 node = self:set(name, node)
470 self.defaultpath[#self.defaultpath+1] = name
473 function Delegator.insert_after(self, name, after)
474 local n = #self.chain + 1
475 for k, v in ipairs(self.chain) do
481 table.insert(self.chain, n, name)
484 function Delegator.set_route(self, ...)
485 local n, chain, route = 0, self.chain, {...}
487 if chain[i] == self.current then
496 for i = n + 1, #chain do
501 function Delegator.get(self, name)
502 local node = self.nodes[name]
504 if type(node) == "string" then
505 node = load(node, name)
508 if type(node) == "table" and getmetatable(node) == nil then
509 node = Compound(unpack(node))
515 function Delegator.parse(self, ...)
516 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
517 if self:_run_hooks("on_cancel") then
522 if not Map.formvalue(self, "cbi.delg.current") then
523 self:_run_hooks("on_init")
527 self.chain = self.chain or self:get_chain()
528 self.current = self.current or self:get_active()
529 self.active = self.active or self:get(self.current)
530 assert(self.active, "Invalid state")
532 local stat = FORM_DONE
533 if type(self.active) ~= "function" then
534 self.active:populate_delegator(self)
535 stat = self.active:parse()
540 if stat > FORM_PROCEED then
541 if Map.formvalue(self, "cbi.delg.back") then
542 newcurrent = self:get_prev(self.current)
544 newcurrent = self:get_next(self.current)
546 elseif stat < FORM_PROCEED then
551 if not Map.formvalue(self, "cbi.submit") then
553 elseif stat > FORM_PROCEED
554 and (not newcurrent or not self:get(newcurrent)) then
555 return self:_run_hook("on_done") or FORM_DONE
557 self.current = newcurrent or self.current
558 self.active = self:get(self.current)
559 if type(self.active) ~= "function" then
560 self.active:populate_delegator(self)
561 local stat = self.active:parse(false)
562 if stat == FORM_SKIP then
563 return self:parse(...)
568 return self:parse(...)
573 function Delegator.get_next(self, state)
574 for k, v in ipairs(self.chain) do
576 return self.chain[k+1]
581 function Delegator.get_prev(self, state)
582 for k, v in ipairs(self.chain) do
584 return self.chain[k-1]
589 function Delegator.get_chain(self)
590 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
591 return type(x) == "table" and x or {x}
594 function Delegator.get_active(self)
595 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
603 Page.__init__ = Node.__init__
604 Page.parse = function() end
608 SimpleForm - A Simple non-UCI form
610 SimpleForm = class(Node)
612 function SimpleForm.__init__(self, config, title, description, data)
613 Node.__init__(self, title, description)
615 self.data = data or {}
616 self.template = "cbi/simpleform"
618 self.pageaction = false
619 self.readinput = true
622 SimpleForm.formvalue = Map.formvalue
623 SimpleForm.formvaluetable = Map.formvaluetable
625 function SimpleForm.parse(self, readinput, ...)
626 self.readinput = (readinput ~= false)
628 if self:formvalue("cbi.skip") then
632 if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
636 if self:submitstate() then
637 Node.parse(self, 1, ...)
641 for k, j in ipairs(self.children) do
642 for i, v in ipairs(j.children) do
644 and (not v.tag_missing or not v.tag_missing[1])
645 and (not v.tag_invalid or not v.tag_invalid[1])
651 not self:submitstate() and FORM_NODATA
652 or valid and FORM_VALID
655 self.dorender = not self.handle
657 local nrender, nstate = self:handle(state, self.data)
658 self.dorender = self.dorender or (nrender ~= false)
659 state = nstate or state
664 function SimpleForm.render(self, ...)
665 if self.dorender then
666 Node.render(self, ...)
670 function SimpleForm.submitstate(self)
671 return self:formvalue("cbi.submit")
674 function SimpleForm.section(self, class, ...)
675 if instanceof(class, AbstractSection) then
676 local obj = class(self, ...)
680 error("class must be a descendent of AbstractSection")
684 -- Creates a child field
685 function SimpleForm.field(self, class, ...)
687 for k, v in ipairs(self.children) do
688 if instanceof(v, SimpleSection) then
694 section = self:section(SimpleSection)
697 if instanceof(class, AbstractValue) then
698 local obj = class(self, section, ...)
699 obj.track_missing = true
703 error("class must be a descendent of AbstractValue")
707 function SimpleForm.set(self, section, option, value)
708 self.data[option] = value
712 function SimpleForm.del(self, section, option)
713 self.data[option] = nil
717 function SimpleForm.get(self, section, option)
718 return self.data[option]
722 function SimpleForm.get_scheme()
727 Form = class(SimpleForm)
729 function Form.__init__(self, ...)
730 SimpleForm.__init__(self, ...)
738 AbstractSection = class(Node)
740 function AbstractSection.__init__(self, map, sectiontype, ...)
741 Node.__init__(self, ...)
742 self.sectiontype = sectiontype
744 self.config = map.config
749 self.tag_invalid = {}
750 self.tag_deperror = {}
754 self.addremove = false
758 -- Define a tab for the section
759 function AbstractSection.tab(self, tab, title, desc)
760 self.tabs = self.tabs or { }
761 self.tab_names = self.tab_names or { }
763 self.tab_names[#self.tab_names+1] = tab
771 -- Check whether the section has tabs
772 function AbstractSection.has_tabs(self)
773 return (self.tabs ~= nil) and (next(self.tabs) ~= nil)
776 -- Appends a new option
777 function AbstractSection.option(self, class, option, ...)
778 if instanceof(class, AbstractValue) then
779 local obj = class(self.map, self, option, ...)
781 self.fields[option] = obj
783 elseif class == true then
784 error("No valid class was given and autodetection failed.")
786 error("class must be a descendant of AbstractValue")
790 -- Appends a new tabbed option
791 function AbstractSection.taboption(self, tab, ...)
793 assert(tab and self.tabs and self.tabs[tab],
794 "Cannot assign option to not existing tab %q" % tostring(tab))
796 local l = self.tabs[tab].childs
797 local o = AbstractSection.option(self, ...)
799 if o then l[#l+1] = o end
804 -- Render a single tab
805 function AbstractSection.render_tab(self, tab, ...)
807 assert(tab and self.tabs and self.tabs[tab],
808 "Cannot render not existing tab %q" % tostring(tab))
811 for k, node in ipairs(self.tabs[tab].childs) do
812 node.last_child = (k == #self.tabs[tab].childs)
817 -- Parse optional options
818 function AbstractSection.parse_optionals(self, section)
819 if not self.optional then
823 self.optionals[section] = {}
825 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
826 for k,v in ipairs(self.children) do
827 if v.optional and not v:cfgvalue(section) and not self:has_tabs() then
828 if field == v.option then
830 self.map.proceed = true
832 table.insert(self.optionals[section], v)
837 if field and #field > 0 and self.dynamic then
838 self:add_dynamic(field)
842 -- Add a dynamic option
843 function AbstractSection.add_dynamic(self, field, optional)
844 local o = self:option(Value, field, field)
845 o.optional = optional
848 -- Parse all dynamic options
849 function AbstractSection.parse_dynamic(self, section)
850 if not self.dynamic then
854 local arr = luci.util.clone(self:cfgvalue(section))
855 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
856 for k, v in pairs(form) do
860 for key,val in pairs(arr) do
863 for i,c in ipairs(self.children) do
864 if c.option == key then
869 if create and key:sub(1, 1) ~= "." then
870 self.map.proceed = true
871 self:add_dynamic(key, true)
876 -- Returns the section's UCI table
877 function AbstractSection.cfgvalue(self, section)
878 return self.map:get(section)
882 function AbstractSection.push_events(self)
883 --luci.util.append(self.map.events, self.events)
884 self.map.changed = true
887 -- Removes the section
888 function AbstractSection.remove(self, section)
889 self.map.proceed = true
890 return self.map:del(section)
893 -- Creates the section
894 function AbstractSection.create(self, section)
898 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
900 section = self.map:add(self.sectiontype)
905 for k,v in pairs(self.children) do
907 self.map:set(section, v.option, v.default)
911 for k,v in pairs(self.defaults) do
912 self.map:set(section, k, v)
916 self.map.proceed = true
922 SimpleSection = class(AbstractSection)
924 function SimpleSection.__init__(self, form, ...)
925 AbstractSection.__init__(self, form, nil, ...)
926 self.template = "cbi/nullsection"
930 Table = class(AbstractSection)
932 function Table.__init__(self, form, data, ...)
933 local datasource = {}
935 datasource.config = "table"
936 self.data = data or {}
938 datasource.formvalue = Map.formvalue
939 datasource.formvaluetable = Map.formvaluetable
940 datasource.readinput = true
942 function datasource.get(self, section, option)
943 return tself.data[section] and tself.data[section][option]
946 function datasource.submitstate(self)
947 return Map.formvalue(self, "cbi.submit")
950 function datasource.del(...)
954 function datasource.get_scheme()
958 AbstractSection.__init__(self, datasource, "table", ...)
959 self.template = "cbi/tblsection"
960 self.rowcolors = true
961 self.anonymous = true
964 function Table.parse(self, readinput)
965 self.map.readinput = (readinput ~= false)
966 for i, k in ipairs(self:cfgsections()) do
967 if self.map:submitstate() then
973 function Table.cfgsections(self)
976 for i, v in luci.util.kspairs(self.data) do
977 table.insert(sections, i)
983 function Table.update(self, data)
990 NamedSection - A fixed configuration section defined by its name
992 NamedSection = class(AbstractSection)
994 function NamedSection.__init__(self, map, section, stype, ...)
995 AbstractSection.__init__(self, map, stype, ...)
998 self.addremove = false
999 self.template = "cbi/nsection"
1000 self.section = section
1003 function NamedSection.parse(self, novld)
1004 local s = self.section
1005 local active = self:cfgvalue(s)
1007 if self.addremove then
1008 local path = self.config.."."..s
1009 if active then -- Remove the section
1010 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1014 else -- Create and apply default values
1015 if self.map:formvalue("cbi.cns."..path) then
1023 AbstractSection.parse_dynamic(self, s)
1024 if self.map:submitstate() then
1027 AbstractSection.parse_optionals(self, s)
1029 if self.changed then
1037 TypedSection - A (set of) configuration section(s) defined by the type
1038 addremove: Defines whether the user can add/remove sections of this type
1039 anonymous: Allow creating anonymous sections
1040 validate: a validation function returning nil if the section is invalid
1042 TypedSection = class(AbstractSection)
1044 function TypedSection.__init__(self, map, type, ...)
1045 AbstractSection.__init__(self, map, type, ...)
1047 self.template = "cbi/tsection"
1049 self.anonymous = false
1052 -- Return all matching UCI sections for this TypedSection
1053 function TypedSection.cfgsections(self)
1055 self.map.uci:foreach(self.map.config, self.sectiontype,
1057 if self:checkscope(section[".name"]) then
1058 table.insert(sections, section[".name"])
1065 -- Limits scope to sections that have certain option => value pairs
1066 function TypedSection.depends(self, option, value)
1067 table.insert(self.deps, {option=option, value=value})
1070 function TypedSection.parse(self, novld)
1071 if self.addremove then
1073 local crval = REMOVE_PREFIX .. self.config
1074 local name = self.map:formvaluetable(crval)
1075 for k,v in pairs(name) do
1076 if k:sub(-2) == ".x" then
1077 k = k:sub(1, #k - 2)
1079 if self:cfgvalue(k) and self:checkscope(k) then
1086 for i, k in ipairs(self:cfgsections()) do
1087 AbstractSection.parse_dynamic(self, k)
1088 if self.map:submitstate() then
1089 Node.parse(self, k, novld)
1091 AbstractSection.parse_optionals(self, k)
1094 if self.addremove then
1097 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1098 local name = self.map:formvalue(crval)
1099 if self.anonymous then
1101 created = self:create()
1105 -- Ignore if it already exists
1106 if self:cfgvalue(name) then
1110 name = self:checkscope(name)
1113 self.err_invalid = true
1116 if name and #name > 0 then
1117 created = self:create(name) and name
1119 self.invalid_cts = true
1126 AbstractSection.parse_optionals(self, created)
1130 if self.sortable then
1131 local stval = RESORT_PREFIX .. self.config .. "." .. self.sectiontype
1132 local order = self.map:formvalue(stval)
1133 if order and #order > 0 then
1136 for sid in util.imatch(order) do
1137 self.map.uci:reorder(self.config, sid, num)
1140 self.changed = (num > 0)
1144 if created or self.changed then
1149 -- Verifies scope of sections
1150 function TypedSection.checkscope(self, section)
1151 -- Check if we are not excluded
1152 if self.filter and not self:filter(section) then
1156 -- Check if at least one dependency is met
1157 if #self.deps > 0 and self:cfgvalue(section) then
1160 for k, v in ipairs(self.deps) do
1161 if self:cfgvalue(section)[v.option] == v.value then
1171 return self:validate(section)
1175 -- Dummy validate function
1176 function TypedSection.validate(self, section)
1182 AbstractValue - An abstract Value Type
1183 null: Value can be empty
1184 valid: A function returning the value if it is valid otherwise nil
1185 depends: A table of option => value pairs of which one must be true
1186 default: The default value
1187 size: The size of the input fields
1188 rmempty: Unset value if empty
1189 optional: This value is optional (see AbstractSection.optionals)
1191 AbstractValue = class(Node)
1193 function AbstractValue.__init__(self, map, section, option, ...)
1194 Node.__init__(self, ...)
1195 self.section = section
1196 self.option = option
1198 self.config = map.config
1199 self.tag_invalid = {}
1200 self.tag_missing = {}
1201 self.tag_reqerror = {}
1205 --self.cast = "string"
1207 self.track_missing = false
1211 self.optional = false
1214 function AbstractValue.prepare(self)
1215 self.cast = self.cast or "string"
1218 -- Add a dependencie to another section field
1219 function AbstractValue.depends(self, field, value)
1221 if type(field) == "string" then
1228 table.insert(self.deps, {deps=deps, add=""})
1231 -- Generates the unique CBID
1232 function AbstractValue.cbid(self, section)
1233 return "cbid."..self.map.config.."."..section.."."..self.option
1236 -- Return whether this object should be created
1237 function AbstractValue.formcreated(self, section)
1238 local key = "cbi.opt."..self.config.."."..section
1239 return (self.map:formvalue(key) == self.option)
1242 -- Returns the formvalue for this object
1243 function AbstractValue.formvalue(self, section)
1244 return self.map:formvalue(self:cbid(section))
1247 function AbstractValue.additional(self, value)
1248 self.optional = value
1251 function AbstractValue.mandatory(self, value)
1252 self.rmempty = not value
1255 function AbstractValue.add_error(self, section, type, msg)
1256 self.error = self.error or { }
1257 self.error[section] = msg or type
1259 self.section.error = self.section.error or { }
1260 self.section.error[section] = self.section.error[section] or { }
1261 table.insert(self.section.error[section], msg or type)
1263 if type == "invalid" then
1264 self.tag_invalid[section] = true
1265 elseif type == "missing" then
1266 self.tag_missing[section] = true
1269 self.tag_error[section] = true
1270 self.map.save = false
1273 function AbstractValue.parse(self, section, novld)
1274 local fvalue = self:formvalue(section)
1275 local cvalue = self:cfgvalue(section)
1277 -- If favlue and cvalue are both tables and have the same content
1278 -- make them identical
1279 if type(fvalue) == "table" and type(cvalue) == "table" then
1280 local equal = #fvalue == #cvalue
1283 if cvalue[i] ~= fvalue[i] then
1293 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1295 fvalue, val_err = self:validate(fvalue, section)
1296 fvalue = self:transform(fvalue)
1298 if not fvalue and not novld then
1299 self:add_error(section, "invalid", val_err)
1302 if fvalue and (self.forcewrite or not (fvalue == cvalue)) then
1303 if self:write(section, fvalue) then
1305 self.section.changed = true
1306 --luci.util.append(self.map.events, self.events)
1309 else -- Unset the UCI or error
1310 if self.rmempty or self.optional then
1311 if self:remove(section) then
1313 self.section.changed = true
1314 --luci.util.append(self.map.events, self.events)
1316 elseif cvalue ~= fvalue and not novld then
1317 -- trigger validator with nil value to get custom user error msg.
1318 local _, val_err = self:validate(nil, section)
1319 self:add_error(section, "missing", val_err)
1324 -- Render if this value exists or if it is mandatory
1325 function AbstractValue.render(self, s, scope)
1326 if not self.optional or self.section:has_tabs() or self:cfgvalue(s) or self:formcreated(s) then
1329 scope.cbid = self:cbid(s)
1330 scope.striptags = luci.util.striptags
1331 scope.pcdata = luci.util.pcdata
1333 scope.ifattr = function(cond,key,val)
1335 return string.format(
1336 ' %s="%s"', tostring(key),
1337 luci.util.pcdata(tostring( val
1339 or (type(self[key]) ~= "function" and self[key])
1347 scope.attr = function(...)
1348 return scope.ifattr( true, ... )
1351 Node.render(self, scope)
1355 -- Return the UCI value of this object
1356 function AbstractValue.cfgvalue(self, section)
1358 if self.tag_error[section] then
1359 value = self:formvalue(section)
1361 value = self.map:get(section, self.option)
1366 elseif not self.cast or self.cast == type(value) then
1368 elseif self.cast == "string" then
1369 if type(value) == "table" then
1372 elseif self.cast == "table" then
1377 -- Validate the form value
1378 function AbstractValue.validate(self, value)
1379 if self.datatype and value then
1381 local dt, ar = self.datatype:match("^(%w+)%(([^%(%)]+)%)")
1385 for a in ar:gmatch("[^%s,]+") do
1392 if dt and datatypes[dt] then
1393 if type(value) == "table" then
1395 for _, v in ipairs(value) do
1396 if v and #v > 0 and not datatypes[dt](v, unpack(args)) then
1401 if not datatypes[dt](value, unpack(args)) then
1411 AbstractValue.transform = AbstractValue.validate
1415 function AbstractValue.write(self, section, value)
1416 return self.map:set(section, self.option, value)
1420 function AbstractValue.remove(self, section)
1421 return self.map:del(section, self.option)
1428 Value - A one-line value
1429 maxlength: The maximum length
1431 Value = class(AbstractValue)
1433 function Value.__init__(self, ...)
1434 AbstractValue.__init__(self, ...)
1435 self.template = "cbi/value"
1440 function Value.reset_values(self)
1445 function Value.value(self, key, val)
1447 table.insert(self.keylist, tostring(key))
1448 table.insert(self.vallist, tostring(val))
1452 -- DummyValue - This does nothing except being there
1453 DummyValue = class(AbstractValue)
1455 function DummyValue.__init__(self, ...)
1456 AbstractValue.__init__(self, ...)
1457 self.template = "cbi/dvalue"
1461 function DummyValue.cfgvalue(self, section)
1464 if type(self.value) == "function" then
1465 value = self:value(section)
1470 value = AbstractValue.cfgvalue(self, section)
1475 function DummyValue.parse(self)
1481 Flag - A flag being enabled or disabled
1483 Flag = class(AbstractValue)
1485 function Flag.__init__(self, ...)
1486 AbstractValue.__init__(self, ...)
1487 self.template = "cbi/fvalue"
1491 self.default = self.disabled
1494 -- A flag can only have two states: set or unset
1495 function Flag.parse(self, section)
1496 local fexists = self.map:formvalue(
1497 FEXIST_PREFIX .. self.config .. "." .. section .. "." .. self.option)
1500 local fvalue = self:formvalue(section) and self.enabled or self.disabled
1501 if fvalue ~= self.default or (not self.optional and not self.rmempty) then
1502 self:write(section, fvalue)
1504 self:remove(section)
1507 self:remove(section)
1511 function Flag.cfgvalue(self, section)
1512 return AbstractValue.cfgvalue(self, section) or self.default
1517 ListValue - A one-line value predefined in a list
1518 widget: The widget that will be used (select, radio)
1520 ListValue = class(AbstractValue)
1522 function ListValue.__init__(self, ...)
1523 AbstractValue.__init__(self, ...)
1524 self.template = "cbi/lvalue"
1529 self.widget = "select"
1532 function ListValue.reset_values(self)
1537 function ListValue.value(self, key, val, ...)
1538 if luci.util.contains(self.keylist, key) then
1543 table.insert(self.keylist, tostring(key))
1544 table.insert(self.vallist, tostring(val))
1546 for i, deps in ipairs({...}) do
1547 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1551 function ListValue.validate(self, val)
1552 if luci.util.contains(self.keylist, val) then
1562 MultiValue - Multiple delimited values
1563 widget: The widget that will be used (select, checkbox)
1564 delimiter: The delimiter that will separate the values (default: " ")
1566 MultiValue = class(AbstractValue)
1568 function MultiValue.__init__(self, ...)
1569 AbstractValue.__init__(self, ...)
1570 self.template = "cbi/mvalue"
1575 self.widget = "checkbox"
1576 self.delimiter = " "
1579 function MultiValue.render(self, ...)
1580 if self.widget == "select" and not self.size then
1581 self.size = #self.vallist
1584 AbstractValue.render(self, ...)
1587 function MultiValue.reset_values(self)
1592 function MultiValue.value(self, key, val)
1593 if luci.util.contains(self.keylist, key) then
1598 table.insert(self.keylist, tostring(key))
1599 table.insert(self.vallist, tostring(val))
1602 function MultiValue.valuelist(self, section)
1603 local val = self:cfgvalue(section)
1605 if not(type(val) == "string") then
1609 return luci.util.split(val, self.delimiter)
1612 function MultiValue.validate(self, val)
1613 val = (type(val) == "table") and val or {val}
1617 for i, value in ipairs(val) do
1618 if luci.util.contains(self.keylist, value) then
1619 result = result and (result .. self.delimiter .. value) or value
1627 StaticList = class(MultiValue)
1629 function StaticList.__init__(self, ...)
1630 MultiValue.__init__(self, ...)
1632 self.valuelist = self.cfgvalue
1634 if not self.override_scheme
1635 and self.map:get_scheme(self.section.sectiontype, self.option) then
1636 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1637 if self.value and vs.values and not self.override_values then
1638 for k, v in pairs(vs.values) do
1645 function StaticList.validate(self, value)
1646 value = (type(value) == "table") and value or {value}
1649 for i, v in ipairs(value) do
1650 if luci.util.contains(self.keylist, v) then
1651 table.insert(valid, v)
1658 DynamicList = class(AbstractValue)
1660 function DynamicList.__init__(self, ...)
1661 AbstractValue.__init__(self, ...)
1662 self.template = "cbi/dynlist"
1668 function DynamicList.reset_values(self)
1673 function DynamicList.value(self, key, val)
1675 table.insert(self.keylist, tostring(key))
1676 table.insert(self.vallist, tostring(val))
1679 function DynamicList.write(self, section, value)
1682 if type(value) == "table" then
1684 for _, x in ipairs(value) do
1685 if x and #x > 0 then
1693 if self.cast == "string" then
1694 value = table.concat(t, " ")
1699 return AbstractValue.write(self, section, value)
1702 function DynamicList.cfgvalue(self, section)
1703 local value = AbstractValue.cfgvalue(self, section)
1705 if type(value) == "string" then
1708 for x in value:gmatch("%S+") do
1719 function DynamicList.formvalue(self, section)
1720 local value = AbstractValue.formvalue(self, section)
1722 if type(value) == "string" then
1723 if self.cast == "string" then
1726 for x in value:gmatch("%S+") do
1740 TextValue - A multi-line value
1743 TextValue = class(AbstractValue)
1745 function TextValue.__init__(self, ...)
1746 AbstractValue.__init__(self, ...)
1747 self.template = "cbi/tvalue"
1753 Button = class(AbstractValue)
1755 function Button.__init__(self, ...)
1756 AbstractValue.__init__(self, ...)
1757 self.template = "cbi/button"
1758 self.inputstyle = nil
1763 FileUpload = class(AbstractValue)
1765 function FileUpload.__init__(self, ...)
1766 AbstractValue.__init__(self, ...)
1767 self.template = "cbi/upload"
1768 if not self.map.upload_fields then
1769 self.map.upload_fields = { self }
1771 self.map.upload_fields[#self.map.upload_fields+1] = self
1775 function FileUpload.formcreated(self, section)
1776 return AbstractValue.formcreated(self, section) or
1777 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1778 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1781 function FileUpload.cfgvalue(self, section)
1782 local val = AbstractValue.cfgvalue(self, section)
1783 if val and fs.access(val) then
1789 function FileUpload.formvalue(self, section)
1790 local val = AbstractValue.formvalue(self, section)
1792 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1793 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1803 function FileUpload.remove(self, section)
1804 local val = AbstractValue.formvalue(self, section)
1805 if val and fs.access(val) then fs.unlink(val) end
1806 return AbstractValue.remove(self, section)
1810 FileBrowser = class(AbstractValue)
1812 function FileBrowser.__init__(self, ...)
1813 AbstractValue.__init__(self, ...)
1814 self.template = "cbi/browser"