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 uci = require("luci.model.uci")
37 local class = util.class
38 local instanceof = util.instanceof
50 CREATE_PREFIX = "cbi.cts."
51 REMOVE_PREFIX = "cbi.rts."
53 -- Loads a CBI map from given file, creating an environment and returns it
54 function load(cbimap, ...)
56 local i18n = require "luci.i18n"
57 require("luci.config")
60 local upldir = "/lib/uci/upload/"
61 local cbidir = luci.util.libpath() .. "/model/cbi/"
63 assert(luci.fs.stat(cbimap) or
64 luci.fs.stat(cbidir..cbimap..".lua") or
65 luci.fs.stat(cbidir..cbimap..".lua.gz"),
68 local func, err = loadfile(cbimap)
70 func, err = loadfile(cbidir..cbimap..".lua") or
71 loadfile(cbidir..cbimap..".lua.gz")
75 luci.i18n.loadc("cbi")
76 luci.i18n.loadc("uvl")
79 translate=i18n.translate,
80 translatef=i18n.translatef,
84 setfenv(func, setmetatable(env, {__index =
86 return rawget(tbl, key) or _M[key] or _G[key]
89 local maps = { func() }
91 local has_upload = false
93 for i, map in ipairs(maps) do
94 if not instanceof(map, Node) then
95 error("CBI map returns no valid map object!")
99 if map.upload_fields then
101 for _, field in ipairs(map.upload_fields) do
103 field.config .. '.' ..
104 field.section.sectiontype .. '.' ..
113 local uci = luci.model.uci.cursor()
114 local prm = luci.http.context.request.message.params
117 luci.http.setfilehandler(
118 function( field, chunk, eof )
119 if not field then return end
120 if field.name and not cbid then
121 local c, s, o = field.name:gmatch(
122 "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
125 if c and s and o then
126 local t = uci:get( c, s )
127 if t and uploads[c.."."..t.."."..o] then
128 local path = upldir .. field.name
129 fd = io.open(path, "w")
138 if field.name == cbid and fd then
154 local function _uvl_validate_section(node, name)
155 local co = node.map:get()
157 luci.uvl.STRICT_UNKNOWN_OPTIONS = false
158 luci.uvl.STRICT_UNKNOWN_SECTIONS = false
160 local function tag_fields(e)
161 if e.option and node.fields[e.option] then
162 if node.fields[e.option].error then
163 node.fields[e.option].error[name] = e
165 node.fields[e.option].error = { [name] = e }
168 for _, c in ipairs(e.childs) do tag_fields(c) end
172 local function tag_section(e)
174 for _, c in ipairs(e.childs or { e }) do
175 if c.childs and not c:is(luci.uvl.errors.ERR_DEPENDENCY) then
176 table.insert( s, c.childs[1]:string() )
178 table.insert( s, c:string() )
185 node.error = { [name] = s }
190 local stat, err = node.map.validator:validate_section(node.config, name, co)
192 node.map.save = false
199 local function _uvl_strip_remote_dependencies(deps)
202 for k, v in pairs(deps) do
203 k = k:gsub("%$config%.%$section%.", "")
204 if k:match("^[%w_]+$") and type(v) == "string" then
213 -- Node pseudo abstract class
216 function Node.__init__(self, title, description)
218 self.title = title or ""
219 self.description = description or ""
220 self.template = "cbi/node"
224 function Node._i18n(self, config, section, option, title, description)
227 if type(luci.i18n) == "table" then
229 local key = config and config:gsub("[^%w]+", "") or ""
231 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
232 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
234 self.title = title or luci.i18n.translate( key, option or section or config )
235 self.description = description or luci.i18n.translate( key .. "_desc", "" )
240 function Node.prepare(self, ...)
241 for k, child in ipairs(self.children) do
246 -- Append child nodes
247 function Node.append(self, obj)
248 table.insert(self.children, obj)
251 -- Parse this node and its children
252 function Node.parse(self, ...)
253 for k, child in ipairs(self.children) do
259 function Node.render(self, scope)
263 luci.template.render(self.template, scope)
266 -- Render the children
267 function Node.render_children(self, ...)
268 for k, node in ipairs(self.children) do
275 A simple template element
277 Template = class(Node)
279 function Template.__init__(self, template)
281 self.template = template
284 function Template.render(self)
285 luci.template.render(self.template, {self=self})
290 Map - A map describing a configuration file
294 function Map.__init__(self, config, ...)
295 Node.__init__(self, ...)
296 Node._i18n(self, config, nil, nil, ...)
299 self.parsechain = {self.config}
300 self.template = "cbi/map"
301 self.apply_on_parse = nil
302 self.readinput = true
305 self.uci = uci.cursor()
310 if not self.uci:load(self.config) then
311 error("Unable to read UCI data: " .. self.config)
314 self.validator = luci.uvl.UVL()
315 self.scheme = self.validator:get_scheme(self.config)
319 function Map.formvalue(self, key)
320 return self.readinput and luci.http.formvalue(key)
323 function Map.formvaluetable(self, key)
324 return self.readinput and luci.http.formvaluetable(key) or {}
327 function Map.get_scheme(self, sectiontype, option)
329 return self.scheme and self.scheme.sections[sectiontype]
331 return self.scheme and self.scheme.variables[sectiontype]
332 and self.scheme.variables[sectiontype][option]
336 function Map.submitstate(self)
337 return self:formvalue("cbi.submit")
340 -- Chain foreign config
341 function Map.chain(self, config)
342 table.insert(self.parsechain, config)
345 function Map.state_handler(self, state)
349 -- Use optimized UCI writing
350 function Map.parse(self, readinput, ...)
351 self.readinput = (readinput ~= false)
353 if self:formvalue("cbi.skip") then
354 self.state = FORM_SKIP
355 return self:state_handler(self.state)
358 Node.parse(self, ...)
361 for i, config in ipairs(self.parsechain) do
362 self.uci:save(config)
364 if self:submitstate() and not self.proceed and (self.flow.autoapply or luci.http.formvalue("cbi.apply")) then
365 for i, config in ipairs(self.parsechain) do
366 self.uci:commit(config)
368 -- Refresh data because commit changes section names
369 self.uci:load(config)
371 if self.apply_on_parse then
372 self.uci:apply(self.parsechain)
374 self._apply = function()
375 local cmd = self.uci:apply(self.parsechain, true)
381 Node.parse(self, true)
384 for i, config in ipairs(self.parsechain) do
385 self.uci:unload(config)
387 if type(self.commit_handler) == "function" then
388 self:commit_handler(self:submitstate())
392 if self:submitstate() then
393 if not self.save then
394 self.state = FORM_INVALID
395 elseif self.proceed then
396 self.state = FORM_PROCEED
398 self.state = self.changed and FORM_CHANGED or FORM_VALID
401 self.state = FORM_NODATA
404 return self:state_handler(self.state)
407 function Map.render(self, ...)
408 Node.render(self, ...)
410 local fp = self._apply()
416 -- Creates a child section
417 function Map.section(self, class, ...)
418 if instanceof(class, AbstractSection) then
419 local obj = class(self, ...)
423 error("class must be a descendent of AbstractSection")
428 function Map.add(self, sectiontype)
429 return self.uci:add(self.config, sectiontype)
433 function Map.set(self, section, option, value)
435 return self.uci:set(self.config, section, option, value)
437 return self.uci:set(self.config, section, value)
442 function Map.del(self, section, option)
444 return self.uci:delete(self.config, section, option)
446 return self.uci:delete(self.config, section)
451 function Map.get(self, section, option)
453 return self.uci:get_all(self.config)
455 return self.uci:get(self.config, section, option)
457 return self.uci:get_all(self.config, section)
464 Compound = class(Node)
466 function Compound.__init__(self, ...)
468 self.template = "cbi/compound"
469 self.children = {...}
472 function Compound.populate_delegator(self, delegator)
473 for _, v in ipairs(self.children) do
474 v.delegator = delegator
478 function Compound.parse(self, ...)
479 local cstate, state = 0
481 for k, child in ipairs(self.children) do
482 cstate = child:parse(...)
483 state = (not state or cstate < state) and cstate or state
491 Delegator - Node controller
493 Delegator = class(Node)
494 function Delegator.__init__(self, ...)
495 Node.__init__(self, ...)
497 self.defaultpath = {}
498 self.pageaction = false
499 self.readinput = true
500 self.allow_back = false
501 self.allow_finish = false
502 self.template = "cbi/delegator"
505 function Delegator.set(self, name, node)
506 if type(node) == "table" and getmetatable(node) == nil then
507 node = Compound(unpack(node))
509 assert(instanceof(node, Compound), "Invalid node")
510 assert(not self.nodes[name], "Duplicate entry")
512 self.nodes[name] = node
515 function Delegator.add(self, name, node)
516 node = self:set(name, node)
517 self.defaultpath[#self.defaultpath+1] = name
520 function Delegator.insert_after(self, name, after)
521 local n = #self.chain
522 for k, v in ipairs(self.chain) do
528 table.insert(self.chain, n, name)
531 function Delegator.get(self, name)
532 return self.nodes[name]
535 function Delegator.parse(self, ...)
537 self.chain = self:get_chain()
538 self.current = self:get_active()
539 self.active = self:get(self.current)
540 assert(self.active, "Invalid state")
542 self.active:populate_delegator(self)
543 if self.active:parse() > FORM_PROCEED then
544 if Map.formvalue(self, "cbi.delg.back") then
545 newcurrent = self:get_prev(self.current)
547 newcurrent = self:get_next(self.current)
551 if not newcurrent or not self:get(newcurrent) then
554 self.current = newcurrent
555 self.active = self:get(self.current)
556 self.active:parse(false)
561 function Delegator.get_next(self, state)
562 for k, v in ipairs(self.chain) do
564 return self.chain[k+1]
569 function Delegator.get_prev(self, state)
570 for k, v in ipairs(self.chain) do
572 return self.chain[k-1]
577 function Delegator.get_chain(self)
578 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
579 return type(x) == "table" and x or {x}
582 function Delegator.get_active(self)
583 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
591 Page.__init__ = Node.__init__
592 Page.parse = function() end
596 SimpleForm - A Simple non-UCI form
598 SimpleForm = class(Node)
600 function SimpleForm.__init__(self, config, title, description, data)
601 Node.__init__(self, title, description)
603 self.data = data or {}
604 self.template = "cbi/simpleform"
606 self.pageaction = false
607 self.readinput = true
610 SimpleForm.formvalue = Map.formvalue
611 SimpleForm.formvaluetable = Map.formvaluetable
613 function SimpleForm.parse(self, readinput, ...)
614 self.readinput = (readinput ~= false)
616 if self:formvalue("cbi.skip") then
620 if self:submitstate() then
621 Node.parse(self, 1, ...)
625 for k, j in ipairs(self.children) do
626 for i, v in ipairs(j.children) do
628 and (not v.tag_missing or not v.tag_missing[1])
629 and (not v.tag_invalid or not v.tag_invalid[1])
635 not self:submitstate() and FORM_NODATA
636 or valid and FORM_VALID
639 self.dorender = not self.handle
641 local nrender, nstate = self:handle(state, self.data)
642 self.dorender = self.dorender or (nrender ~= false)
643 state = nstate or state
648 function SimpleForm.render(self, ...)
649 if self.dorender then
650 Node.render(self, ...)
654 function SimpleForm.submitstate(self)
655 return self:formvalue("cbi.submit")
658 function SimpleForm.section(self, class, ...)
659 if instanceof(class, AbstractSection) then
660 local obj = class(self, ...)
664 error("class must be a descendent of AbstractSection")
668 -- Creates a child field
669 function SimpleForm.field(self, class, ...)
671 for k, v in ipairs(self.children) do
672 if instanceof(v, SimpleSection) then
678 section = self:section(SimpleSection)
681 if instanceof(class, AbstractValue) then
682 local obj = class(self, section, ...)
683 obj.track_missing = true
687 error("class must be a descendent of AbstractValue")
691 function SimpleForm.set(self, section, option, value)
692 self.data[option] = value
696 function SimpleForm.del(self, section, option)
697 self.data[option] = nil
701 function SimpleForm.get(self, section, option)
702 return self.data[option]
706 function SimpleForm.get_scheme()
711 Form = class(SimpleForm)
713 function Form.__init__(self, ...)
714 SimpleForm.__init__(self, ...)
722 AbstractSection = class(Node)
724 function AbstractSection.__init__(self, map, sectiontype, ...)
725 Node.__init__(self, ...)
726 self.sectiontype = sectiontype
728 self.config = map.config
733 self.tag_invalid = {}
734 self.tag_deperror = {}
738 self.addremove = false
742 -- Appends a new option
743 function AbstractSection.option(self, class, option, ...)
744 -- Autodetect from UVL
745 if class == true and self.map:get_scheme(self.sectiontype, option) then
746 local vs = self.map:get_scheme(self.sectiontype, option)
747 if vs.type == "boolean" then
749 elseif vs.type == "list" then
751 elseif vs.type == "enum" or vs.type == "reference" then
758 if instanceof(class, AbstractValue) then
759 local obj = class(self.map, self, option, ...)
761 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
764 self.fields[option] = obj
766 elseif class == true then
767 error("No valid class was given and autodetection failed.")
769 error("class must be a descendant of AbstractValue")
773 -- Parse optional options
774 function AbstractSection.parse_optionals(self, section)
775 if not self.optional then
779 self.optionals[section] = {}
781 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
782 for k,v in ipairs(self.children) do
783 if v.optional and not v:cfgvalue(section) then
784 if field == v.option then
787 self.map.proceed = true
788 table.insert(self.optionals[section], v)
793 if field and #field > 0 and self.dynamic then
794 self:add_dynamic(field)
798 -- Add a dynamic option
799 function AbstractSection.add_dynamic(self, field, optional)
800 local o = self:option(Value, field, field)
801 o.optional = optional
804 -- Parse all dynamic options
805 function AbstractSection.parse_dynamic(self, section)
806 if not self.dynamic then
810 local arr = luci.util.clone(self:cfgvalue(section))
811 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
812 for k, v in pairs(form) do
816 for key,val in pairs(arr) do
819 for i,c in ipairs(self.children) do
820 if c.option == key then
825 if create and key:sub(1, 1) ~= "." then
826 self.map.proceed = true
827 self:add_dynamic(key, true)
832 -- Returns the section's UCI table
833 function AbstractSection.cfgvalue(self, section)
834 return self.map:get(section)
838 function AbstractSection.push_events(self)
839 --luci.util.append(self.map.events, self.events)
840 self.map.changed = true
843 -- Removes the section
844 function AbstractSection.remove(self, section)
845 self.map.proceed = true
846 return self.map:del(section)
849 -- Creates the section
850 function AbstractSection.create(self, section)
854 stat = section:match("^%w+$") and self.map:set(section, nil, self.sectiontype)
856 section = self.map:add(self.sectiontype)
861 for k,v in pairs(self.children) do
863 self.map:set(section, v.option, v.default)
867 for k,v in pairs(self.defaults) do
868 self.map:set(section, k, v)
872 self.map.proceed = true
878 SimpleSection = class(AbstractSection)
880 function SimpleSection.__init__(self, form, ...)
881 AbstractSection.__init__(self, form, nil, ...)
882 self.template = "cbi/nullsection"
886 Table = class(AbstractSection)
888 function Table.__init__(self, form, data, ...)
889 local datasource = {}
891 datasource.config = "table"
892 self.data = data or {}
894 datasource.formvalue = Map.formvalue
895 datasource.formvaluetable = Map.formvaluetable
896 datasource.readinput = true
898 function datasource.get(self, section, option)
899 return tself.data[section] and tself.data[section][option]
902 function datasource.submitstate(self)
903 return Map.formvalue(self, "cbi.submit")
906 function datasource.del(...)
910 function datasource.get_scheme()
914 AbstractSection.__init__(self, datasource, "table", ...)
915 self.template = "cbi/tblsection"
916 self.rowcolors = true
917 self.anonymous = true
920 function Table.parse(self, readinput)
921 self.map.readinput = (readinput ~= false)
922 for i, k in ipairs(self:cfgsections()) do
923 if self.map:submitstate() then
929 function Table.cfgsections(self)
932 for i, v in luci.util.kspairs(self.data) do
933 table.insert(sections, i)
939 function Table.update(self, data)
946 NamedSection - A fixed configuration section defined by its name
948 NamedSection = class(AbstractSection)
950 function NamedSection.__init__(self, map, section, stype, ...)
951 AbstractSection.__init__(self, map, stype, ...)
952 Node._i18n(self, map.config, section, nil, ...)
955 self.addremove = false
957 -- Use defaults from UVL
958 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
959 local vs = self.map:get_scheme(self.sectiontype)
960 self.addremove = not vs.unique and not vs.required
961 self.dynamic = vs.dynamic
962 self.title = self.title or vs.title
963 self.description = self.description or vs.descr
966 self.template = "cbi/nsection"
967 self.section = section
970 function NamedSection.parse(self, novld)
971 local s = self.section
972 local active = self:cfgvalue(s)
974 if self.addremove then
975 local path = self.config.."."..s
976 if active then -- Remove the section
977 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
981 else -- Create and apply default values
982 if self.map:formvalue("cbi.cns."..path) then
990 AbstractSection.parse_dynamic(self, s)
991 if self.map:submitstate() then
994 if not novld and not self.override_scheme and self.map.scheme then
995 _uvl_validate_section(self, s)
998 AbstractSection.parse_optionals(self, s)
1000 if self.changed then
1008 TypedSection - A (set of) configuration section(s) defined by the type
1009 addremove: Defines whether the user can add/remove sections of this type
1010 anonymous: Allow creating anonymous sections
1011 validate: a validation function returning nil if the section is invalid
1013 TypedSection = class(AbstractSection)
1015 function TypedSection.__init__(self, map, type, ...)
1016 AbstractSection.__init__(self, map, type, ...)
1017 Node._i18n(self, map.config, type, nil, ...)
1019 self.template = "cbi/tsection"
1021 self.anonymous = false
1023 -- Use defaults from UVL
1024 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1025 local vs = self.map:get_scheme(self.sectiontype)
1026 self.addremove = not vs.unique and not vs.required
1027 self.dynamic = vs.dynamic
1028 self.anonymous = not vs.named
1029 self.title = self.title or vs.title
1030 self.description = self.description or vs.descr
1034 -- Return all matching UCI sections for this TypedSection
1035 function TypedSection.cfgsections(self)
1037 self.map.uci:foreach(self.map.config, self.sectiontype,
1039 if self:checkscope(section[".name"]) then
1040 table.insert(sections, section[".name"])
1047 -- Limits scope to sections that have certain option => value pairs
1048 function TypedSection.depends(self, option, value)
1049 table.insert(self.deps, {option=option, value=value})
1052 function TypedSection.parse(self, novld)
1053 if self.addremove then
1055 local crval = REMOVE_PREFIX .. self.config
1056 local name = self.map:formvaluetable(crval)
1057 for k,v in pairs(name) do
1058 if k:sub(-2) == ".x" then
1059 k = k:sub(1, #k - 2)
1061 if self:cfgvalue(k) and self:checkscope(k) then
1068 for i, k in ipairs(self:cfgsections()) do
1069 AbstractSection.parse_dynamic(self, k)
1070 if self.map:submitstate() then
1071 Node.parse(self, k, novld)
1073 if not novld and not self.override_scheme and self.map.scheme then
1074 _uvl_validate_section(self, k)
1077 AbstractSection.parse_optionals(self, k)
1080 if self.addremove then
1083 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1084 local name = self.map:formvalue(crval)
1085 if self.anonymous then
1087 created = self:create()
1091 -- Ignore if it already exists
1092 if self:cfgvalue(name) then
1096 name = self:checkscope(name)
1099 self.err_invalid = true
1102 if name and #name > 0 then
1103 created = self:create(name) and name
1105 self.invalid_cts = true
1112 AbstractSection.parse_optionals(self, created)
1116 if created or self.changed then
1121 -- Verifies scope of sections
1122 function TypedSection.checkscope(self, section)
1123 -- Check if we are not excluded
1124 if self.filter and not self:filter(section) then
1128 -- Check if at least one dependency is met
1129 if #self.deps > 0 and self:cfgvalue(section) then
1132 for k, v in ipairs(self.deps) do
1133 if self:cfgvalue(section)[v.option] == v.value then
1143 return self:validate(section)
1147 -- Dummy validate function
1148 function TypedSection.validate(self, section)
1154 AbstractValue - An abstract Value Type
1155 null: Value can be empty
1156 valid: A function returning the value if it is valid otherwise nil
1157 depends: A table of option => value pairs of which one must be true
1158 default: The default value
1159 size: The size of the input fields
1160 rmempty: Unset value if empty
1161 optional: This value is optional (see AbstractSection.optionals)
1163 AbstractValue = class(Node)
1165 function AbstractValue.__init__(self, map, section, option, ...)
1166 Node.__init__(self, ...)
1167 self.section = section
1168 self.option = option
1170 self.config = map.config
1171 self.tag_invalid = {}
1172 self.tag_missing = {}
1173 self.tag_reqerror = {}
1176 --self.cast = "string"
1178 self.track_missing = false
1182 self.optional = false
1185 function AbstractValue.prepare(self)
1186 -- Use defaults from UVL
1187 if not self.override_scheme
1188 and self.map:get_scheme(self.section.sectiontype, self.option) then
1189 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1190 if self.cast == nil then
1191 self.cast = (vs.type == "list") and "list" or "string"
1193 self.title = self.title or vs.title
1194 self.description = self.description or vs.descr
1195 if self.default == nil then
1196 self.default = vs.default
1199 if vs.depends and not self.override_dependencies then
1200 for i, deps in ipairs(vs.depends) do
1201 deps = _uvl_strip_remote_dependencies(deps)
1209 self.cast = self.cast or "string"
1212 -- Add a dependencie to another section field
1213 function AbstractValue.depends(self, field, value)
1215 if type(field) == "string" then
1222 table.insert(self.deps, {deps=deps, add=""})
1225 -- Generates the unique CBID
1226 function AbstractValue.cbid(self, section)
1227 return "cbid."..self.map.config.."."..section.."."..self.option
1230 -- Return whether this object should be created
1231 function AbstractValue.formcreated(self, section)
1232 local key = "cbi.opt."..self.config.."."..section
1233 return (self.map:formvalue(key) == self.option)
1236 -- Returns the formvalue for this object
1237 function AbstractValue.formvalue(self, section)
1238 return self.map:formvalue(self:cbid(section))
1241 function AbstractValue.additional(self, value)
1242 self.optional = value
1245 function AbstractValue.mandatory(self, value)
1246 self.rmempty = not value
1249 function AbstractValue.parse(self, section, novld)
1250 local fvalue = self:formvalue(section)
1251 local cvalue = self:cfgvalue(section)
1253 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1254 fvalue = self:transform(self:validate(fvalue, section))
1255 if not fvalue and not novld then
1257 self.error[section] = "invalid"
1259 self.error = { [section] = "invalid" }
1261 self.map.save = false
1263 if fvalue and not (fvalue == cvalue) then
1264 if self:write(section, fvalue) then
1266 self.section.changed = true
1267 --luci.util.append(self.map.events, self.events)
1270 else -- Unset the UCI or error
1271 if self.rmempty or self.optional then
1272 if self:remove(section) then
1274 self.section.changed = true
1275 --luci.util.append(self.map.events, self.events)
1277 elseif cvalue ~= fvalue and not novld then
1278 self:write(section, fvalue or "")
1280 self.error[section] = "missing"
1282 self.error = { [section] = "missing" }
1284 self.map.save = false
1289 -- Render if this value exists or if it is mandatory
1290 function AbstractValue.render(self, s, scope)
1291 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1294 scope.cbid = self:cbid(s)
1295 scope.striptags = luci.util.striptags
1297 scope.ifattr = function(cond,key,val)
1299 return string.format(
1300 ' %s="%s"', tostring(key),
1301 luci.util.pcdata(tostring( val
1303 or (type(self[key]) ~= "function" and self[key])
1311 scope.attr = function(...)
1312 return scope.ifattr( true, ... )
1315 Node.render(self, scope)
1319 -- Return the UCI value of this object
1320 function AbstractValue.cfgvalue(self, section)
1321 local value = self.map:get(section, self.option)
1324 elseif not self.cast or self.cast == type(value) then
1326 elseif self.cast == "string" then
1327 if type(value) == "table" then
1330 elseif self.cast == "table" then
1331 return luci.util.split(value, "%s+", nil, true)
1335 -- Validate the form value
1336 function AbstractValue.validate(self, value)
1340 AbstractValue.transform = AbstractValue.validate
1344 function AbstractValue.write(self, section, value)
1345 return self.map:set(section, self.option, value)
1349 function AbstractValue.remove(self, section)
1350 return self.map:del(section, self.option)
1357 Value - A one-line value
1358 maxlength: The maximum length
1360 Value = class(AbstractValue)
1362 function Value.__init__(self, ...)
1363 AbstractValue.__init__(self, ...)
1364 self.template = "cbi/value"
1369 function Value.value(self, key, val)
1371 table.insert(self.keylist, tostring(key))
1372 table.insert(self.vallist, tostring(val))
1376 -- DummyValue - This does nothing except being there
1377 DummyValue = class(AbstractValue)
1379 function DummyValue.__init__(self, ...)
1380 AbstractValue.__init__(self, ...)
1381 self.template = "cbi/dvalue"
1385 function DummyValue.cfgvalue(self, section)
1388 if type(self.value) == "function" then
1389 value = self:value(section)
1394 value = AbstractValue.cfgvalue(self, section)
1399 function DummyValue.parse(self)
1405 Flag - A flag being enabled or disabled
1407 Flag = class(AbstractValue)
1409 function Flag.__init__(self, ...)
1410 AbstractValue.__init__(self, ...)
1411 self.template = "cbi/fvalue"
1417 -- A flag can only have two states: set or unset
1418 function Flag.parse(self, section)
1419 local fvalue = self:formvalue(section)
1422 fvalue = self.enabled
1424 fvalue = self.disabled
1427 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1428 if not(fvalue == self:cfgvalue(section)) then
1429 self:write(section, fvalue)
1432 self:remove(section)
1439 ListValue - A one-line value predefined in a list
1440 widget: The widget that will be used (select, radio)
1442 ListValue = class(AbstractValue)
1444 function ListValue.__init__(self, ...)
1445 AbstractValue.__init__(self, ...)
1446 self.template = "cbi/lvalue"
1451 self.widget = "select"
1454 function ListValue.prepare(self, ...)
1455 AbstractValue.prepare(self, ...)
1456 if not self.override_scheme
1457 and self.map:get_scheme(self.section.sectiontype, self.option) then
1458 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1459 if self.value and vs.valuelist and not self.override_values then
1460 for k, v in ipairs(vs.valuelist) do
1462 if not self.override_dependencies
1463 and vs.enum_depends and vs.enum_depends[v.value] then
1464 for i, dep in ipairs(vs.enum_depends[v.value]) do
1465 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1468 self:value(v.value, v.title or v.value, unpack(deps))
1474 function ListValue.value(self, key, val, ...)
1475 if luci.util.contains(self.keylist, key) then
1480 table.insert(self.keylist, tostring(key))
1481 table.insert(self.vallist, tostring(val))
1483 for i, deps in ipairs({...}) do
1484 table.insert(self.deps, {add = "-"..key, deps=deps})
1488 function ListValue.validate(self, val)
1489 if luci.util.contains(self.keylist, val) then
1499 MultiValue - Multiple delimited values
1500 widget: The widget that will be used (select, checkbox)
1501 delimiter: The delimiter that will separate the values (default: " ")
1503 MultiValue = class(AbstractValue)
1505 function MultiValue.__init__(self, ...)
1506 AbstractValue.__init__(self, ...)
1507 self.template = "cbi/mvalue"
1512 self.widget = "checkbox"
1513 self.delimiter = " "
1516 function MultiValue.render(self, ...)
1517 if self.widget == "select" and not self.size then
1518 self.size = #self.vallist
1521 AbstractValue.render(self, ...)
1524 function MultiValue.value(self, key, val)
1525 if luci.util.contains(self.keylist, key) then
1530 table.insert(self.keylist, tostring(key))
1531 table.insert(self.vallist, tostring(val))
1534 function MultiValue.valuelist(self, section)
1535 local val = self:cfgvalue(section)
1537 if not(type(val) == "string") then
1541 return luci.util.split(val, self.delimiter)
1544 function MultiValue.validate(self, val)
1545 val = (type(val) == "table") and val or {val}
1549 for i, value in ipairs(val) do
1550 if luci.util.contains(self.keylist, value) then
1551 result = result and (result .. self.delimiter .. value) or value
1559 StaticList = class(MultiValue)
1561 function StaticList.__init__(self, ...)
1562 MultiValue.__init__(self, ...)
1564 self.valuelist = self.cfgvalue
1566 if not self.override_scheme
1567 and self.map:get_scheme(self.section.sectiontype, self.option) then
1568 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1569 if self.value and vs.values and not self.override_values then
1570 for k, v in pairs(vs.values) do
1577 function StaticList.validate(self, value)
1578 value = (type(value) == "table") and value or {value}
1581 for i, v in ipairs(value) do
1582 if luci.util.contains(self.keylist, v) then
1583 table.insert(valid, v)
1590 DynamicList = class(AbstractValue)
1592 function DynamicList.__init__(self, ...)
1593 AbstractValue.__init__(self, ...)
1594 self.template = "cbi/dynlist"
1600 function DynamicList.value(self, key, val)
1602 table.insert(self.keylist, tostring(key))
1603 table.insert(self.vallist, tostring(val))
1606 function DynamicList.write(self, ...)
1607 self.map.proceed = true
1608 return AbstractValue.write(self, ...)
1611 function DynamicList.formvalue(self, section)
1612 local value = AbstractValue.formvalue(self, section)
1613 value = (type(value) == "table") and value or {value}
1616 for i, v in ipairs(value) do
1618 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1619 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1620 table.insert(valid, v)
1629 TextValue - A multi-line value
1632 TextValue = class(AbstractValue)
1634 function TextValue.__init__(self, ...)
1635 AbstractValue.__init__(self, ...)
1636 self.template = "cbi/tvalue"
1642 Button = class(AbstractValue)
1644 function Button.__init__(self, ...)
1645 AbstractValue.__init__(self, ...)
1646 self.template = "cbi/button"
1647 self.inputstyle = nil
1652 FileUpload = class(AbstractValue)
1654 function FileUpload.__init__(self, ...)
1655 AbstractValue.__init__(self, ...)
1656 self.template = "cbi/upload"
1657 if not self.map.upload_fields then
1658 self.map.upload_fields = { self }
1660 self.map.upload_fields[#self.map.upload_fields+1] = self
1664 function FileUpload.formcreated(self, section)
1665 return AbstractValue.formcreated(self, section) or
1666 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1667 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1670 function FileUpload.cfgvalue(self, section)
1671 local val = AbstractValue.cfgvalue(self, section)
1672 if val and luci.fs.access(val) then
1678 function FileUpload.formvalue(self, section)
1679 local val = AbstractValue.formvalue(self, section)
1681 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1682 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1692 function FileUpload.remove(self, section)
1693 local val = AbstractValue.formvalue(self, section)
1694 if val and luci.fs.access(val) then luci.fs.unlink(val) end
1695 return AbstractValue.remove(self, section)
1699 FileBrowser = class(AbstractValue)
1701 function FileBrowser.__init__(self, ...)
1702 AbstractValue.__init__(self, ...)
1703 self.template = "cbi/browser"