2 LuCI - Configuration Bind Interface
5 Offers an interface for binding configuration values to certain
6 data types. Supports value and range validation and basic dependencies.
12 Copyright 2008 Steven Barth <steven@midlink.org>
14 Licensed under the Apache License, Version 2.0 (the "License");
15 you may not use this file except in compliance with the License.
16 You may obtain a copy of the License at
18 http://www.apache.org/licenses/LICENSE-2.0
20 Unless required by applicable law or agreed to in writing, software
21 distributed under the License is distributed on an "AS IS" BASIS,
22 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 See the License for the specific language governing permissions and
24 limitations under the License.
27 module("luci.cbi", package.seeall)
29 require("luci.template")
30 local util = require("luci.util")
35 --local event = require "luci.sys.event"
36 local fs = require("nixio.fs")
37 local uci = require("luci.model.uci")
38 local class = util.class
39 local instanceof = util.instanceof
51 CREATE_PREFIX = "cbi.cts."
52 REMOVE_PREFIX = "cbi.rts."
54 -- Loads a CBI map from given file, creating an environment and returns it
55 function load(cbimap, ...)
56 local fs = require "nixio.fs"
57 local i18n = require "luci.i18n"
58 require("luci.config")
61 local upldir = "/lib/uci/upload/"
62 local cbidir = luci.util.libpath() .. "/model/cbi/"
65 if fs.access(cbimap) then
66 func, err = loadfile(cbimap)
67 elseif fs.access(cbidir..cbimap..".lua") then
68 func, err = loadfile(cbidir..cbimap..".lua")
69 elseif fs.access(cbidir..cbimap..".lua.gz") then
70 func, err = loadfile(cbidir..cbimap..".lua.gz")
72 func, err = nil, "Model '" .. cbimap .. "' not found!"
77 luci.i18n.loadc("cbi")
78 luci.i18n.loadc("uvl")
81 translate=i18n.translate,
82 translatef=i18n.translatef,
86 setfenv(func, setmetatable(env, {__index =
88 return rawget(tbl, key) or _M[key] or _G[key]
91 local maps = { func() }
93 local has_upload = false
95 for i, map in ipairs(maps) do
96 if not instanceof(map, Node) then
97 error("CBI map returns no valid map object!")
101 if map.upload_fields then
103 for _, field in ipairs(map.upload_fields) do
105 field.config .. '.' ..
106 field.section.sectiontype .. '.' ..
115 local uci = luci.model.uci.cursor()
116 local prm = luci.http.context.request.message.params
119 luci.http.setfilehandler(
120 function( field, chunk, eof )
121 if not field then return end
122 if field.name and not cbid then
123 local c, s, o = field.name:gmatch(
124 "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
127 if c and s and o then
128 local t = uci:get( c, s )
129 if t and uploads[c.."."..t.."."..o] then
130 local path = upldir .. field.name
131 fd = io.open(path, "w")
140 if field.name == cbid and fd then
156 local function _uvl_validate_section(node, name)
157 local co = node.map:get()
159 luci.uvl.STRICT_UNKNOWN_OPTIONS = false
160 luci.uvl.STRICT_UNKNOWN_SECTIONS = false
162 local function tag_fields(e)
163 if e.option and node.fields[e.option] then
164 if node.fields[e.option].error then
165 node.fields[e.option].error[name] = e
167 node.fields[e.option].error = { [name] = e }
170 for _, c in ipairs(e.childs) do tag_fields(c) end
174 local function tag_section(e)
176 for _, c in ipairs(e.childs or { e }) do
177 if c.childs and not c:is(luci.uvl.errors.ERR_DEPENDENCY) then
178 table.insert( s, c.childs[1]:string() )
180 table.insert( s, c:string() )
187 node.error = { [name] = s }
192 local stat, err = node.map.validator:validate_section(node.config, name, co)
194 node.map.save = false
201 local function _uvl_strip_remote_dependencies(deps)
204 for k, v in pairs(deps) do
205 k = k:gsub("%$config%.%$section%.", "")
206 if k:match("^[%w_]+$") and type(v) == "string" then
215 -- Node pseudo abstract class
218 function Node.__init__(self, title, description)
220 self.title = title or ""
221 self.description = description or ""
222 self.template = "cbi/node"
226 function Node._i18n(self, config, section, option, title, description)
229 if type(luci.i18n) == "table" then
231 local key = config and config:gsub("[^%w]+", "") or ""
233 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
234 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
236 self.title = title or luci.i18n.translate( key, option or section or config )
237 self.description = description or luci.i18n.translate( key .. "_desc", "" )
242 function Node.prepare(self, ...)
243 for k, child in ipairs(self.children) do
248 -- Append child nodes
249 function Node.append(self, obj)
250 table.insert(self.children, obj)
253 -- Parse this node and its children
254 function Node.parse(self, ...)
255 for k, child in ipairs(self.children) do
261 function Node.render(self, scope)
265 luci.template.render(self.template, scope)
268 -- Render the children
269 function Node.render_children(self, ...)
270 for k, node in ipairs(self.children) do
277 A simple template element
279 Template = class(Node)
281 function Template.__init__(self, template)
283 self.template = template
286 function Template.render(self)
287 luci.template.render(self.template, {self=self})
290 function Template.parse(self, readinput)
291 self.readinput = (readinput ~= false)
292 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
297 Map - A map describing a configuration file
301 function Map.__init__(self, config, ...)
302 Node.__init__(self, ...)
303 Node._i18n(self, config, nil, nil, ...)
306 self.parsechain = {self.config}
307 self.template = "cbi/map"
308 self.apply_on_parse = nil
309 self.readinput = true
313 self.uci = uci.cursor()
318 if not self.uci:load(self.config) then
319 error("Unable to read UCI data: " .. self.config)
322 self.validator = luci.uvl.UVL()
323 self.scheme = self.validator:get_scheme(self.config)
327 function Map.formvalue(self, key)
328 return self.readinput and luci.http.formvalue(key)
331 function Map.formvaluetable(self, key)
332 return self.readinput and luci.http.formvaluetable(key) or {}
335 function Map.get_scheme(self, sectiontype, option)
337 return self.scheme and self.scheme.sections[sectiontype]
339 return self.scheme and self.scheme.variables[sectiontype]
340 and self.scheme.variables[sectiontype][option]
344 function Map.submitstate(self)
345 return self:formvalue("cbi.submit")
348 -- Chain foreign config
349 function Map.chain(self, config)
350 table.insert(self.parsechain, config)
353 function Map.state_handler(self, state)
357 -- Use optimized UCI writing
358 function Map.parse(self, readinput, ...)
359 self.readinput = (readinput ~= false)
361 if self:formvalue("cbi.skip") then
362 self.state = FORM_SKIP
363 return self:state_handler(self.state)
366 Node.parse(self, ...)
369 for i, config in ipairs(self.parsechain) do
370 self.uci:save(config)
372 if self:submitstate() and not self.proceed and (self.flow.autoapply or luci.http.formvalue("cbi.apply")) then
373 for i, config in ipairs(self.parsechain) do
374 self.uci:commit(config)
376 -- Refresh data because commit changes section names
377 self.uci:load(config)
379 if self.apply_on_parse then
380 self.uci:apply(self.parsechain)
382 self._apply = function()
383 local cmd = self.uci:apply(self.parsechain, true)
389 Node.parse(self, true)
392 for i, config in ipairs(self.parsechain) do
393 self.uci:unload(config)
395 if type(self.commit_handler) == "function" then
396 self:commit_handler(self:submitstate())
400 if self:submitstate() then
401 if not self.save then
402 self.state = FORM_INVALID
403 elseif self.proceed then
404 self.state = FORM_PROCEED
406 self.state = self.changed and FORM_CHANGED or FORM_VALID
409 self.state = FORM_NODATA
412 return self:state_handler(self.state)
415 function Map.render(self, ...)
416 Node.render(self, ...)
418 local fp = self._apply()
424 -- Creates a child section
425 function Map.section(self, class, ...)
426 if instanceof(class, AbstractSection) then
427 local obj = class(self, ...)
431 error("class must be a descendent of AbstractSection")
436 function Map.add(self, sectiontype)
437 return self.uci:add(self.config, sectiontype)
441 function Map.set(self, section, option, value)
443 return self.uci:set(self.config, section, option, value)
445 return self.uci:set(self.config, section, value)
450 function Map.del(self, section, option)
452 return self.uci:delete(self.config, section, option)
454 return self.uci:delete(self.config, section)
459 function Map.get(self, section, option)
461 return self.uci:get_all(self.config)
463 return self.uci:get(self.config, section, option)
465 return self.uci:get_all(self.config, section)
472 Compound = class(Node)
474 function Compound.__init__(self, ...)
476 self.template = "cbi/compound"
477 self.children = {...}
480 function Compound.populate_delegator(self, delegator)
481 for _, v in ipairs(self.children) do
482 v.delegator = delegator
486 function Compound.parse(self, ...)
487 local cstate, state = 0
489 for k, child in ipairs(self.children) do
490 cstate = child:parse(...)
491 state = (not state or cstate < state) and cstate or state
499 Delegator - Node controller
501 Delegator = class(Node)
502 function Delegator.__init__(self, ...)
503 Node.__init__(self, ...)
505 self.defaultpath = {}
506 self.pageaction = false
507 self.readinput = true
508 self.allow_reset = false
509 self.allow_back = false
510 self.allow_finish = false
511 self.template = "cbi/delegator"
514 function Delegator.set(self, name, node)
515 if type(node) == "table" and getmetatable(node) == nil then
516 node = Compound(unpack(node))
518 assert(type(node) == "function" or instanceof(node, Compound), "Invalid")
519 assert(not self.nodes[name], "Duplicate entry")
521 self.nodes[name] = node
524 function Delegator.add(self, name, node)
525 node = self:set(name, node)
526 self.defaultpath[#self.defaultpath+1] = name
529 function Delegator.insert_after(self, name, after)
530 local n = #self.chain
531 for k, v in ipairs(self.chain) do
537 table.insert(self.chain, n, name)
540 function Delegator.set_route(self, ...)
541 local n, chain, route = 0, self.chain, {...}
543 if chain[i] == self.current then
552 for i = n + 1, #chain do
557 function Delegator.get(self, name)
558 return self.nodes[name]
561 function Delegator.parse(self, ...)
563 self.chain = self.chain or self:get_chain()
564 self.current = self.current or self:get_active()
565 self.active = self.active or self:get(self.current)
566 assert(self.active, "Invalid state")
568 local stat = FORM_DONE
569 if type(self.active) ~= "function" then
570 self.active:populate_delegator(self)
571 stat = self.active:parse()
576 if stat > FORM_PROCEED then
577 if Map.formvalue(self, "cbi.delg.back") then
578 newcurrent = self:get_prev(self.current)
580 newcurrent = self:get_next(self.current)
582 elseif stat < FORM_PROCEED then
587 if not Map.formvalue(self, "cbi.submit") then
589 elseif not newcurrent or not self:get(newcurrent) then
592 self.current = newcurrent
593 self.active = self:get(self.current)
594 if type(self.active) ~= "function" then
595 self.active:parse(false)
598 return self:parse(...)
603 function Delegator.get_next(self, state)
604 for k, v in ipairs(self.chain) do
606 return self.chain[k+1]
611 function Delegator.get_prev(self, state)
612 for k, v in ipairs(self.chain) do
614 return self.chain[k-1]
619 function Delegator.get_chain(self)
620 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
621 return type(x) == "table" and x or {x}
624 function Delegator.get_active(self)
625 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
633 Page.__init__ = Node.__init__
634 Page.parse = function() end
638 SimpleForm - A Simple non-UCI form
640 SimpleForm = class(Node)
642 function SimpleForm.__init__(self, config, title, description, data)
643 Node.__init__(self, title, description)
645 self.data = data or {}
646 self.template = "cbi/simpleform"
648 self.pageaction = false
649 self.readinput = true
652 SimpleForm.formvalue = Map.formvalue
653 SimpleForm.formvaluetable = Map.formvaluetable
655 function SimpleForm.parse(self, readinput, ...)
656 self.readinput = (readinput ~= false)
658 if self:formvalue("cbi.skip") then
662 if self:submitstate() then
663 Node.parse(self, 1, ...)
667 for k, j in ipairs(self.children) do
668 for i, v in ipairs(j.children) do
670 and (not v.tag_missing or not v.tag_missing[1])
671 and (not v.tag_invalid or not v.tag_invalid[1])
677 not self:submitstate() and FORM_NODATA
678 or valid and FORM_VALID
681 self.dorender = not self.handle
683 local nrender, nstate = self:handle(state, self.data)
684 self.dorender = self.dorender or (nrender ~= false)
685 state = nstate or state
690 function SimpleForm.render(self, ...)
691 if self.dorender then
692 Node.render(self, ...)
696 function SimpleForm.submitstate(self)
697 return self:formvalue("cbi.submit")
700 function SimpleForm.section(self, class, ...)
701 if instanceof(class, AbstractSection) then
702 local obj = class(self, ...)
706 error("class must be a descendent of AbstractSection")
710 -- Creates a child field
711 function SimpleForm.field(self, class, ...)
713 for k, v in ipairs(self.children) do
714 if instanceof(v, SimpleSection) then
720 section = self:section(SimpleSection)
723 if instanceof(class, AbstractValue) then
724 local obj = class(self, section, ...)
725 obj.track_missing = true
729 error("class must be a descendent of AbstractValue")
733 function SimpleForm.set(self, section, option, value)
734 self.data[option] = value
738 function SimpleForm.del(self, section, option)
739 self.data[option] = nil
743 function SimpleForm.get(self, section, option)
744 return self.data[option]
748 function SimpleForm.get_scheme()
753 Form = class(SimpleForm)
755 function Form.__init__(self, ...)
756 SimpleForm.__init__(self, ...)
764 AbstractSection = class(Node)
766 function AbstractSection.__init__(self, map, sectiontype, ...)
767 Node.__init__(self, ...)
768 self.sectiontype = sectiontype
770 self.config = map.config
775 self.tag_invalid = {}
776 self.tag_deperror = {}
780 self.addremove = false
784 -- Appends a new option
785 function AbstractSection.option(self, class, option, ...)
786 -- Autodetect from UVL
787 if class == true and self.map:get_scheme(self.sectiontype, option) then
788 local vs = self.map:get_scheme(self.sectiontype, option)
789 if vs.type == "boolean" then
791 elseif vs.type == "list" then
793 elseif vs.type == "enum" or vs.type == "reference" then
800 if instanceof(class, AbstractValue) then
801 local obj = class(self.map, self, option, ...)
803 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
806 self.fields[option] = obj
808 elseif class == true then
809 error("No valid class was given and autodetection failed.")
811 error("class must be a descendant of AbstractValue")
815 -- Parse optional options
816 function AbstractSection.parse_optionals(self, section)
817 if not self.optional then
821 self.optionals[section] = {}
823 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
824 for k,v in ipairs(self.children) do
825 if v.optional and not v:cfgvalue(section) then
826 if field == v.option then
828 self.map.proceed = true
830 table.insert(self.optionals[section], v)
835 if field and #field > 0 and self.dynamic then
836 self:add_dynamic(field)
840 -- Add a dynamic option
841 function AbstractSection.add_dynamic(self, field, optional)
842 local o = self:option(Value, field, field)
843 o.optional = optional
846 -- Parse all dynamic options
847 function AbstractSection.parse_dynamic(self, section)
848 if not self.dynamic then
852 local arr = luci.util.clone(self:cfgvalue(section))
853 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
854 for k, v in pairs(form) do
858 for key,val in pairs(arr) do
861 for i,c in ipairs(self.children) do
862 if c.option == key then
867 if create and key:sub(1, 1) ~= "." then
868 self.map.proceed = true
869 self:add_dynamic(key, true)
874 -- Returns the section's UCI table
875 function AbstractSection.cfgvalue(self, section)
876 return self.map:get(section)
880 function AbstractSection.push_events(self)
881 --luci.util.append(self.map.events, self.events)
882 self.map.changed = true
885 -- Removes the section
886 function AbstractSection.remove(self, section)
887 self.map.proceed = true
888 return self.map:del(section)
891 -- Creates the section
892 function AbstractSection.create(self, section)
896 stat = section:match("^%w+$") and self.map:set(section, nil, self.sectiontype)
898 section = self.map:add(self.sectiontype)
903 for k,v in pairs(self.children) do
905 self.map:set(section, v.option, v.default)
909 for k,v in pairs(self.defaults) do
910 self.map:set(section, k, v)
914 self.map.proceed = true
920 SimpleSection = class(AbstractSection)
922 function SimpleSection.__init__(self, form, ...)
923 AbstractSection.__init__(self, form, nil, ...)
924 self.template = "cbi/nullsection"
928 Table = class(AbstractSection)
930 function Table.__init__(self, form, data, ...)
931 local datasource = {}
933 datasource.config = "table"
934 self.data = data or {}
936 datasource.formvalue = Map.formvalue
937 datasource.formvaluetable = Map.formvaluetable
938 datasource.readinput = true
940 function datasource.get(self, section, option)
941 return tself.data[section] and tself.data[section][option]
944 function datasource.submitstate(self)
945 return Map.formvalue(self, "cbi.submit")
948 function datasource.del(...)
952 function datasource.get_scheme()
956 AbstractSection.__init__(self, datasource, "table", ...)
957 self.template = "cbi/tblsection"
958 self.rowcolors = true
959 self.anonymous = true
962 function Table.parse(self, readinput)
963 self.map.readinput = (readinput ~= false)
964 for i, k in ipairs(self:cfgsections()) do
965 if self.map:submitstate() then
971 function Table.cfgsections(self)
974 for i, v in luci.util.kspairs(self.data) do
975 table.insert(sections, i)
981 function Table.update(self, data)
988 NamedSection - A fixed configuration section defined by its name
990 NamedSection = class(AbstractSection)
992 function NamedSection.__init__(self, map, section, stype, ...)
993 AbstractSection.__init__(self, map, stype, ...)
994 Node._i18n(self, map.config, section, nil, ...)
997 self.addremove = false
999 -- Use defaults from UVL
1000 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1001 local vs = self.map:get_scheme(self.sectiontype)
1002 self.addremove = not vs.unique and not vs.required
1003 self.dynamic = vs.dynamic
1004 self.title = self.title or vs.title
1005 self.description = self.description or vs.descr
1008 self.template = "cbi/nsection"
1009 self.section = section
1012 function NamedSection.parse(self, novld)
1013 local s = self.section
1014 local active = self:cfgvalue(s)
1016 if self.addremove then
1017 local path = self.config.."."..s
1018 if active then -- Remove the section
1019 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1023 else -- Create and apply default values
1024 if self.map:formvalue("cbi.cns."..path) then
1032 AbstractSection.parse_dynamic(self, s)
1033 if self.map:submitstate() then
1036 if not novld and not self.override_scheme and self.map.scheme then
1037 _uvl_validate_section(self, s)
1040 AbstractSection.parse_optionals(self, s)
1042 if self.changed then
1050 TypedSection - A (set of) configuration section(s) defined by the type
1051 addremove: Defines whether the user can add/remove sections of this type
1052 anonymous: Allow creating anonymous sections
1053 validate: a validation function returning nil if the section is invalid
1055 TypedSection = class(AbstractSection)
1057 function TypedSection.__init__(self, map, type, ...)
1058 AbstractSection.__init__(self, map, type, ...)
1059 Node._i18n(self, map.config, type, nil, ...)
1061 self.template = "cbi/tsection"
1063 self.anonymous = false
1065 -- Use defaults from UVL
1066 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1067 local vs = self.map:get_scheme(self.sectiontype)
1068 self.addremove = not vs.unique and not vs.required
1069 self.dynamic = vs.dynamic
1070 self.anonymous = not vs.named
1071 self.title = self.title or vs.title
1072 self.description = self.description or vs.descr
1076 -- Return all matching UCI sections for this TypedSection
1077 function TypedSection.cfgsections(self)
1079 self.map.uci:foreach(self.map.config, self.sectiontype,
1081 if self:checkscope(section[".name"]) then
1082 table.insert(sections, section[".name"])
1089 -- Limits scope to sections that have certain option => value pairs
1090 function TypedSection.depends(self, option, value)
1091 table.insert(self.deps, {option=option, value=value})
1094 function TypedSection.parse(self, novld)
1095 if self.addremove then
1097 local crval = REMOVE_PREFIX .. self.config
1098 local name = self.map:formvaluetable(crval)
1099 for k,v in pairs(name) do
1100 if k:sub(-2) == ".x" then
1101 k = k:sub(1, #k - 2)
1103 if self:cfgvalue(k) and self:checkscope(k) then
1110 for i, k in ipairs(self:cfgsections()) do
1111 AbstractSection.parse_dynamic(self, k)
1112 if self.map:submitstate() then
1113 Node.parse(self, k, novld)
1115 if not novld and not self.override_scheme and self.map.scheme then
1116 _uvl_validate_section(self, k)
1119 AbstractSection.parse_optionals(self, k)
1122 if self.addremove then
1125 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1126 local name = self.map:formvalue(crval)
1127 if self.anonymous then
1129 created = self:create()
1133 -- Ignore if it already exists
1134 if self:cfgvalue(name) then
1138 name = self:checkscope(name)
1141 self.err_invalid = true
1144 if name and #name > 0 then
1145 created = self:create(name) and name
1147 self.invalid_cts = true
1154 AbstractSection.parse_optionals(self, created)
1158 if created or self.changed then
1163 -- Verifies scope of sections
1164 function TypedSection.checkscope(self, section)
1165 -- Check if we are not excluded
1166 if self.filter and not self:filter(section) then
1170 -- Check if at least one dependency is met
1171 if #self.deps > 0 and self:cfgvalue(section) then
1174 for k, v in ipairs(self.deps) do
1175 if self:cfgvalue(section)[v.option] == v.value then
1185 return self:validate(section)
1189 -- Dummy validate function
1190 function TypedSection.validate(self, section)
1196 AbstractValue - An abstract Value Type
1197 null: Value can be empty
1198 valid: A function returning the value if it is valid otherwise nil
1199 depends: A table of option => value pairs of which one must be true
1200 default: The default value
1201 size: The size of the input fields
1202 rmempty: Unset value if empty
1203 optional: This value is optional (see AbstractSection.optionals)
1205 AbstractValue = class(Node)
1207 function AbstractValue.__init__(self, map, section, option, ...)
1208 Node.__init__(self, ...)
1209 self.section = section
1210 self.option = option
1212 self.config = map.config
1213 self.tag_invalid = {}
1214 self.tag_missing = {}
1215 self.tag_reqerror = {}
1218 --self.cast = "string"
1220 self.track_missing = false
1224 self.optional = false
1227 function AbstractValue.prepare(self)
1228 -- Use defaults from UVL
1229 if not self.override_scheme
1230 and self.map:get_scheme(self.section.sectiontype, self.option) then
1231 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1232 if self.cast == nil then
1233 self.cast = (vs.type == "list") and "list" or "string"
1235 self.title = self.title or vs.title
1236 self.description = self.description or vs.descr
1237 if self.default == nil then
1238 self.default = vs.default
1241 if vs.depends and not self.override_dependencies then
1242 for i, deps in ipairs(vs.depends) do
1243 deps = _uvl_strip_remote_dependencies(deps)
1251 self.cast = self.cast or "string"
1254 -- Add a dependencie to another section field
1255 function AbstractValue.depends(self, field, value)
1257 if type(field) == "string" then
1264 table.insert(self.deps, {deps=deps, add=""})
1267 -- Generates the unique CBID
1268 function AbstractValue.cbid(self, section)
1269 return "cbid."..self.map.config.."."..section.."."..self.option
1272 -- Return whether this object should be created
1273 function AbstractValue.formcreated(self, section)
1274 local key = "cbi.opt."..self.config.."."..section
1275 return (self.map:formvalue(key) == self.option)
1278 -- Returns the formvalue for this object
1279 function AbstractValue.formvalue(self, section)
1280 return self.map:formvalue(self:cbid(section))
1283 function AbstractValue.additional(self, value)
1284 self.optional = value
1287 function AbstractValue.mandatory(self, value)
1288 self.rmempty = not value
1291 function AbstractValue.parse(self, section, novld)
1292 local fvalue = self:formvalue(section)
1293 local cvalue = self:cfgvalue(section)
1295 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1296 fvalue = self:transform(self:validate(fvalue, section))
1297 if not fvalue and not novld then
1299 self.error[section] = "invalid"
1301 self.error = { [section] = "invalid" }
1303 if self.section.error then
1304 table.insert(self.section.error[section], "invalid")
1306 self.section.error = {[section] = {"invalid"}}
1308 self.map.save = false
1310 if fvalue and not (fvalue == cvalue) then
1311 if self:write(section, fvalue) then
1313 self.section.changed = true
1314 --luci.util.append(self.map.events, self.events)
1317 else -- Unset the UCI or error
1318 if self.rmempty or self.optional then
1319 if self:remove(section) then
1321 self.section.changed = true
1322 --luci.util.append(self.map.events, self.events)
1324 elseif cvalue ~= fvalue and not novld then
1325 self:write(section, fvalue or "")
1327 self.error[section] = "missing"
1329 self.error = { [section] = "missing" }
1331 self.map.save = false
1336 -- Render if this value exists or if it is mandatory
1337 function AbstractValue.render(self, s, scope)
1338 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1341 scope.cbid = self:cbid(s)
1342 scope.striptags = luci.util.striptags
1344 scope.ifattr = function(cond,key,val)
1346 return string.format(
1347 ' %s="%s"', tostring(key),
1348 luci.util.pcdata(tostring( val
1350 or (type(self[key]) ~= "function" and self[key])
1358 scope.attr = function(...)
1359 return scope.ifattr( true, ... )
1362 Node.render(self, scope)
1366 -- Return the UCI value of this object
1367 function AbstractValue.cfgvalue(self, section)
1368 local value = self.map:get(section, self.option)
1371 elseif not self.cast or self.cast == type(value) then
1373 elseif self.cast == "string" then
1374 if type(value) == "table" then
1377 elseif self.cast == "table" then
1378 return luci.util.split(value, "%s+", nil, true)
1382 -- Validate the form value
1383 function AbstractValue.validate(self, value)
1387 AbstractValue.transform = AbstractValue.validate
1391 function AbstractValue.write(self, section, value)
1392 return self.map:set(section, self.option, value)
1396 function AbstractValue.remove(self, section)
1397 return self.map:del(section, self.option)
1404 Value - A one-line value
1405 maxlength: The maximum length
1407 Value = class(AbstractValue)
1409 function Value.__init__(self, ...)
1410 AbstractValue.__init__(self, ...)
1411 self.template = "cbi/value"
1416 function Value.value(self, key, val)
1418 table.insert(self.keylist, tostring(key))
1419 table.insert(self.vallist, tostring(val))
1423 -- DummyValue - This does nothing except being there
1424 DummyValue = class(AbstractValue)
1426 function DummyValue.__init__(self, ...)
1427 AbstractValue.__init__(self, ...)
1428 self.template = "cbi/dvalue"
1432 function DummyValue.cfgvalue(self, section)
1435 if type(self.value) == "function" then
1436 value = self:value(section)
1441 value = AbstractValue.cfgvalue(self, section)
1446 function DummyValue.parse(self)
1452 Flag - A flag being enabled or disabled
1454 Flag = class(AbstractValue)
1456 function Flag.__init__(self, ...)
1457 AbstractValue.__init__(self, ...)
1458 self.template = "cbi/fvalue"
1464 -- A flag can only have two states: set or unset
1465 function Flag.parse(self, section)
1466 local fvalue = self:formvalue(section)
1469 fvalue = self.enabled
1471 fvalue = self.disabled
1474 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1475 if not(fvalue == self:cfgvalue(section)) then
1476 self:write(section, fvalue)
1479 self:remove(section)
1486 ListValue - A one-line value predefined in a list
1487 widget: The widget that will be used (select, radio)
1489 ListValue = class(AbstractValue)
1491 function ListValue.__init__(self, ...)
1492 AbstractValue.__init__(self, ...)
1493 self.template = "cbi/lvalue"
1498 self.widget = "select"
1501 function ListValue.prepare(self, ...)
1502 AbstractValue.prepare(self, ...)
1503 if not self.override_scheme
1504 and self.map:get_scheme(self.section.sectiontype, self.option) then
1505 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1506 if self.value and vs.valuelist and not self.override_values then
1507 for k, v in ipairs(vs.valuelist) do
1509 if not self.override_dependencies
1510 and vs.enum_depends and vs.enum_depends[v.value] then
1511 for i, dep in ipairs(vs.enum_depends[v.value]) do
1512 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1515 self:value(v.value, v.title or v.value, unpack(deps))
1521 function ListValue.value(self, key, val, ...)
1522 if luci.util.contains(self.keylist, key) then
1527 table.insert(self.keylist, tostring(key))
1528 table.insert(self.vallist, tostring(val))
1530 for i, deps in ipairs({...}) do
1531 table.insert(self.deps, {add = "-"..key, deps=deps})
1535 function ListValue.validate(self, val)
1536 if luci.util.contains(self.keylist, val) then
1546 MultiValue - Multiple delimited values
1547 widget: The widget that will be used (select, checkbox)
1548 delimiter: The delimiter that will separate the values (default: " ")
1550 MultiValue = class(AbstractValue)
1552 function MultiValue.__init__(self, ...)
1553 AbstractValue.__init__(self, ...)
1554 self.template = "cbi/mvalue"
1559 self.widget = "checkbox"
1560 self.delimiter = " "
1563 function MultiValue.render(self, ...)
1564 if self.widget == "select" and not self.size then
1565 self.size = #self.vallist
1568 AbstractValue.render(self, ...)
1571 function MultiValue.value(self, key, val)
1572 if luci.util.contains(self.keylist, key) then
1577 table.insert(self.keylist, tostring(key))
1578 table.insert(self.vallist, tostring(val))
1581 function MultiValue.valuelist(self, section)
1582 local val = self:cfgvalue(section)
1584 if not(type(val) == "string") then
1588 return luci.util.split(val, self.delimiter)
1591 function MultiValue.validate(self, val)
1592 val = (type(val) == "table") and val or {val}
1596 for i, value in ipairs(val) do
1597 if luci.util.contains(self.keylist, value) then
1598 result = result and (result .. self.delimiter .. value) or value
1606 StaticList = class(MultiValue)
1608 function StaticList.__init__(self, ...)
1609 MultiValue.__init__(self, ...)
1611 self.valuelist = self.cfgvalue
1613 if not self.override_scheme
1614 and self.map:get_scheme(self.section.sectiontype, self.option) then
1615 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1616 if self.value and vs.values and not self.override_values then
1617 for k, v in pairs(vs.values) do
1624 function StaticList.validate(self, value)
1625 value = (type(value) == "table") and value or {value}
1628 for i, v in ipairs(value) do
1629 if luci.util.contains(self.keylist, v) then
1630 table.insert(valid, v)
1637 DynamicList = class(AbstractValue)
1639 function DynamicList.__init__(self, ...)
1640 AbstractValue.__init__(self, ...)
1641 self.template = "cbi/dynlist"
1647 function DynamicList.value(self, key, val)
1649 table.insert(self.keylist, tostring(key))
1650 table.insert(self.vallist, tostring(val))
1653 function DynamicList.write(self, ...)
1654 self.map.proceed = true
1655 return AbstractValue.write(self, ...)
1658 function DynamicList.formvalue(self, section)
1659 local value = AbstractValue.formvalue(self, section)
1660 value = (type(value) == "table") and value or {value}
1663 for i, v in ipairs(value) do
1665 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1666 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1667 table.insert(valid, v)
1676 TextValue - A multi-line value
1679 TextValue = class(AbstractValue)
1681 function TextValue.__init__(self, ...)
1682 AbstractValue.__init__(self, ...)
1683 self.template = "cbi/tvalue"
1689 Button = class(AbstractValue)
1691 function Button.__init__(self, ...)
1692 AbstractValue.__init__(self, ...)
1693 self.template = "cbi/button"
1694 self.inputstyle = nil
1699 FileUpload = class(AbstractValue)
1701 function FileUpload.__init__(self, ...)
1702 AbstractValue.__init__(self, ...)
1703 self.template = "cbi/upload"
1704 if not self.map.upload_fields then
1705 self.map.upload_fields = { self }
1707 self.map.upload_fields[#self.map.upload_fields+1] = self
1711 function FileUpload.formcreated(self, section)
1712 return AbstractValue.formcreated(self, section) or
1713 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1714 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1717 function FileUpload.cfgvalue(self, section)
1718 local val = AbstractValue.cfgvalue(self, section)
1719 if val and fs.access(val) then
1725 function FileUpload.formvalue(self, section)
1726 local val = AbstractValue.formvalue(self, section)
1728 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1729 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1739 function FileUpload.remove(self, section)
1740 local val = AbstractValue.formvalue(self, section)
1741 if val and fs.access(val) then fs.unlink(val) end
1742 return AbstractValue.remove(self, section)
1746 FileBrowser = class(AbstractValue)
1748 function FileBrowser.__init__(self, ...)
1749 AbstractValue.__init__(self, ...)
1750 self.template = "cbi/browser"