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."
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(cbidir..cbimap..".lua") then
66 func, err = loadfile(cbidir..cbimap..".lua")
67 elseif fs.access(cbimap) then
68 func, err = loadfile(cbimap)
70 func, err = nil, "Model '" .. cbimap .. "' not found!"
75 luci.i18n.loadc("base")
78 translate=i18n.translate,
79 translatef=i18n.translatef,
83 setfenv(func, setmetatable(env, {__index =
85 return rawget(tbl, key) or _M[key] or _G[key]
88 local maps = { func() }
90 local has_upload = false
92 for i, map in ipairs(maps) do
93 if not instanceof(map, Node) then
94 error("CBI map returns no valid map object!")
98 if map.upload_fields then
100 for _, field in ipairs(map.upload_fields) do
102 field.config .. '.' ..
103 field.section.sectiontype .. '.' ..
112 local uci = luci.model.uci.cursor()
113 local prm = luci.http.context.request.message.params
116 luci.http.setfilehandler(
117 function( field, chunk, eof )
118 if not field then return end
119 if field.name and not cbid then
120 local c, s, o = field.name:gmatch(
121 "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
124 if c and s and o then
125 local t = uci:get( c, s )
126 if t and uploads[c.."."..t.."."..o] then
127 local path = upldir .. field.name
128 fd = io.open(path, "w")
137 if field.name == cbid and fd then
154 -- Node pseudo abstract class
157 function Node.__init__(self, title, description)
159 self.title = title or ""
160 self.description = description or ""
161 self.template = "cbi/node"
165 function Node._run_hook(self, hook)
166 if type(self[hook]) == "function" then
167 return self[hook](self)
171 function Node._run_hooks(self, ...)
174 for _, f in ipairs(arg) do
175 if type(self[f]) == "function" then
184 function Node.prepare(self, ...)
185 for k, child in ipairs(self.children) do
190 -- Append child nodes
191 function Node.append(self, obj)
192 table.insert(self.children, obj)
195 -- Parse this node and its children
196 function Node.parse(self, ...)
197 for k, child in ipairs(self.children) do
203 function Node.render(self, scope)
207 luci.template.render(self.template, scope)
210 -- Render the children
211 function Node.render_children(self, ...)
212 for k, node in ipairs(self.children) do
219 A simple template element
221 Template = class(Node)
223 function Template.__init__(self, template)
225 self.template = template
228 function Template.render(self)
229 luci.template.render(self.template, {self=self})
232 function Template.parse(self, readinput)
233 self.readinput = (readinput ~= false)
234 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
239 Map - A map describing a configuration file
243 function Map.__init__(self, config, ...)
244 Node.__init__(self, ...)
247 self.parsechain = {self.config}
248 self.template = "cbi/map"
249 self.apply_on_parse = nil
250 self.readinput = true
254 self.uci = uci.cursor()
259 if not self.uci:load(self.config) then
260 error("Unable to read UCI data: " .. self.config)
264 function Map.formvalue(self, key)
265 return self.readinput and luci.http.formvalue(key)
268 function Map.formvaluetable(self, key)
269 return self.readinput and luci.http.formvaluetable(key) or {}
272 function Map.get_scheme(self, sectiontype, option)
274 return self.scheme and self.scheme.sections[sectiontype]
276 return self.scheme and self.scheme.variables[sectiontype]
277 and self.scheme.variables[sectiontype][option]
281 function Map.submitstate(self)
282 return self:formvalue("cbi.submit")
285 -- Chain foreign config
286 function Map.chain(self, config)
287 table.insert(self.parsechain, config)
290 function Map.state_handler(self, state)
294 -- Use optimized UCI writing
295 function Map.parse(self, readinput, ...)
296 self.readinput = (readinput ~= false)
297 self:_run_hooks("on_parse")
299 if self:formvalue("cbi.skip") then
300 self.state = FORM_SKIP
301 return self:state_handler(self.state)
304 Node.parse(self, ...)
307 for i, config in ipairs(self.parsechain) do
308 self.uci:save(config)
310 if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
311 self:_run_hooks("on_before_commit")
312 for i, config in ipairs(self.parsechain) do
313 self.uci:commit(config)
315 -- Refresh data because commit changes section names
316 self.uci:load(config)
318 self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
319 if self.apply_on_parse then
320 self.uci:apply(self.parsechain)
321 self:_run_hooks("on_apply", "on_after_apply")
323 self._apply = function()
324 local cmd = self.uci:apply(self.parsechain, true)
330 Node.parse(self, true)
333 for i, config in ipairs(self.parsechain) do
334 self.uci:unload(config)
336 if type(self.commit_handler) == "function" then
337 self:commit_handler(self:submitstate())
341 if self:submitstate() then
342 if not self.save then
343 self.state = FORM_INVALID
344 elseif self.proceed then
345 self.state = FORM_PROCEED
347 self.state = self.changed and FORM_CHANGED or FORM_VALID
350 self.state = FORM_NODATA
353 return self:state_handler(self.state)
356 function Map.render(self, ...)
357 self:_run_hooks("on_init")
358 Node.render(self, ...)
360 local fp = self._apply()
363 self:_run_hooks("on_apply")
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)
386 return self.uci:set(self.config, section, option, value)
388 return self.uci:set(self.config, section, value)
393 function Map.del(self, section, option)
395 return self.uci:delete(self.config, section, option)
397 return self.uci:delete(self.config, section)
402 function Map.get(self, section, option)
404 return self.uci:get_all(self.config)
406 return self.uci:get(self.config, section, option)
408 return self.uci:get_all(self.config, section)
415 Compound = class(Node)
417 function Compound.__init__(self, ...)
419 self.template = "cbi/compound"
420 self.children = {...}
423 function Compound.populate_delegator(self, delegator)
424 for _, v in ipairs(self.children) do
425 v.delegator = delegator
429 function Compound.parse(self, ...)
430 local cstate, state = 0
432 for k, child in ipairs(self.children) do
433 cstate = child:parse(...)
434 state = (not state or cstate < state) and cstate or state
442 Delegator - Node controller
444 Delegator = class(Node)
445 function Delegator.__init__(self, ...)
446 Node.__init__(self, ...)
448 self.defaultpath = {}
449 self.pageaction = false
450 self.readinput = true
451 self.allow_reset = false
452 self.allow_cancel = false
453 self.allow_back = false
454 self.allow_finish = false
455 self.template = "cbi/delegator"
458 function Delegator.set(self, name, node)
459 assert(not self.nodes[name], "Duplicate entry")
461 self.nodes[name] = node
464 function Delegator.add(self, name, node)
465 node = self:set(name, node)
466 self.defaultpath[#self.defaultpath+1] = name
469 function Delegator.insert_after(self, name, after)
470 local n = #self.chain + 1
471 for k, v in ipairs(self.chain) do
477 table.insert(self.chain, n, name)
480 function Delegator.set_route(self, ...)
481 local n, chain, route = 0, self.chain, {...}
483 if chain[i] == self.current then
492 for i = n + 1, #chain do
497 function Delegator.get(self, name)
498 local node = self.nodes[name]
500 if type(node) == "string" then
501 node = load(node, name)
504 if type(node) == "table" and getmetatable(node) == nil then
505 node = Compound(unpack(node))
511 function Delegator.parse(self, ...)
512 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
513 if self:_run_hooks("on_cancel") then
518 if not Map.formvalue(self, "cbi.delg.current") then
519 self:_run_hooks("on_init")
523 self.chain = self.chain or self:get_chain()
524 self.current = self.current or self:get_active()
525 self.active = self.active or self:get(self.current)
526 assert(self.active, "Invalid state")
528 local stat = FORM_DONE
529 if type(self.active) ~= "function" then
530 self.active:populate_delegator(self)
531 stat = self.active:parse()
536 if stat > FORM_PROCEED then
537 if Map.formvalue(self, "cbi.delg.back") then
538 newcurrent = self:get_prev(self.current)
540 newcurrent = self:get_next(self.current)
542 elseif stat < FORM_PROCEED then
547 if not Map.formvalue(self, "cbi.submit") then
549 elseif stat > FORM_PROCEED
550 and (not newcurrent or not self:get(newcurrent)) then
551 return self:_run_hook("on_done") or FORM_DONE
553 self.current = newcurrent or self.current
554 self.active = self:get(self.current)
555 if type(self.active) ~= "function" then
556 self.active:populate_delegator(self)
557 local stat = self.active:parse(false)
558 if stat == FORM_SKIP then
559 return self:parse(...)
564 return self:parse(...)
569 function Delegator.get_next(self, state)
570 for k, v in ipairs(self.chain) do
572 return self.chain[k+1]
577 function Delegator.get_prev(self, state)
578 for k, v in ipairs(self.chain) do
580 return self.chain[k-1]
585 function Delegator.get_chain(self)
586 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
587 return type(x) == "table" and x or {x}
590 function Delegator.get_active(self)
591 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
599 Page.__init__ = Node.__init__
600 Page.parse = function() end
604 SimpleForm - A Simple non-UCI form
606 SimpleForm = class(Node)
608 function SimpleForm.__init__(self, config, title, description, data)
609 Node.__init__(self, title, description)
611 self.data = data or {}
612 self.template = "cbi/simpleform"
614 self.pageaction = false
615 self.readinput = true
618 SimpleForm.formvalue = Map.formvalue
619 SimpleForm.formvaluetable = Map.formvaluetable
621 function SimpleForm.parse(self, readinput, ...)
622 self.readinput = (readinput ~= false)
624 if self:formvalue("cbi.skip") then
628 if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
632 if self:submitstate() then
633 Node.parse(self, 1, ...)
637 for k, j in ipairs(self.children) do
638 for i, v in ipairs(j.children) do
640 and (not v.tag_missing or not v.tag_missing[1])
641 and (not v.tag_invalid or not v.tag_invalid[1])
647 not self:submitstate() and FORM_NODATA
648 or valid and FORM_VALID
651 self.dorender = not self.handle
653 local nrender, nstate = self:handle(state, self.data)
654 self.dorender = self.dorender or (nrender ~= false)
655 state = nstate or state
660 function SimpleForm.render(self, ...)
661 if self.dorender then
662 Node.render(self, ...)
666 function SimpleForm.submitstate(self)
667 return self:formvalue("cbi.submit")
670 function SimpleForm.section(self, class, ...)
671 if instanceof(class, AbstractSection) then
672 local obj = class(self, ...)
676 error("class must be a descendent of AbstractSection")
680 -- Creates a child field
681 function SimpleForm.field(self, class, ...)
683 for k, v in ipairs(self.children) do
684 if instanceof(v, SimpleSection) then
690 section = self:section(SimpleSection)
693 if instanceof(class, AbstractValue) then
694 local obj = class(self, section, ...)
695 obj.track_missing = true
699 error("class must be a descendent of AbstractValue")
703 function SimpleForm.set(self, section, option, value)
704 self.data[option] = value
708 function SimpleForm.del(self, section, option)
709 self.data[option] = nil
713 function SimpleForm.get(self, section, option)
714 return self.data[option]
718 function SimpleForm.get_scheme()
723 Form = class(SimpleForm)
725 function Form.__init__(self, ...)
726 SimpleForm.__init__(self, ...)
734 AbstractSection = class(Node)
736 function AbstractSection.__init__(self, map, sectiontype, ...)
737 Node.__init__(self, ...)
738 self.sectiontype = sectiontype
740 self.config = map.config
745 self.tag_invalid = {}
746 self.tag_deperror = {}
750 self.addremove = false
754 -- Define a tab for the section
755 function AbstractSection.tab(self, tab, title, desc)
756 self.tabs = self.tabs or { }
757 self.tab_names = self.tab_names or { }
759 self.tab_names[#self.tab_names+1] = tab
767 -- Appends a new option
768 function AbstractSection.option(self, class, option, ...)
769 if instanceof(class, AbstractValue) then
770 local obj = class(self.map, self, option, ...)
772 self.fields[option] = obj
774 elseif class == true then
775 error("No valid class was given and autodetection failed.")
777 error("class must be a descendant of AbstractValue")
781 -- Appends a new tabbed option
782 function AbstractSection.taboption(self, tab, ...)
784 assert(tab and self.tabs and self.tabs[tab],
785 "Cannot assign option to not existing tab %q" % tostring(tab))
787 local l = self.tabs[tab].childs
788 local o = AbstractSection.option(self, ...)
790 if o then l[#l+1] = o end
795 -- Render a single tab
796 function AbstractSection.render_tab(self, tab, ...)
798 assert(tab and self.tabs and self.tabs[tab],
799 "Cannot render not existing tab %q" % tostring(tab))
801 for _, node in ipairs(self.tabs[tab].childs) do
806 -- Parse optional options
807 function AbstractSection.parse_optionals(self, section)
808 if not self.optional then
812 self.optionals[section] = {}
814 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
815 for k,v in ipairs(self.children) do
816 if v.optional and not v:cfgvalue(section) and not next(self.tabs) then
817 if field == v.option then
819 self.map.proceed = true
821 table.insert(self.optionals[section], v)
826 if field and #field > 0 and self.dynamic then
827 self:add_dynamic(field)
831 -- Add a dynamic option
832 function AbstractSection.add_dynamic(self, field, optional)
833 local o = self:option(Value, field, field)
834 o.optional = optional
837 -- Parse all dynamic options
838 function AbstractSection.parse_dynamic(self, section)
839 if not self.dynamic then
843 local arr = luci.util.clone(self:cfgvalue(section))
844 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
845 for k, v in pairs(form) do
849 for key,val in pairs(arr) do
852 for i,c in ipairs(self.children) do
853 if c.option == key then
858 if create and key:sub(1, 1) ~= "." then
859 self.map.proceed = true
860 self:add_dynamic(key, true)
865 -- Returns the section's UCI table
866 function AbstractSection.cfgvalue(self, section)
867 return self.map:get(section)
871 function AbstractSection.push_events(self)
872 --luci.util.append(self.map.events, self.events)
873 self.map.changed = true
876 -- Removes the section
877 function AbstractSection.remove(self, section)
878 self.map.proceed = true
879 return self.map:del(section)
882 -- Creates the section
883 function AbstractSection.create(self, section)
887 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
889 section = self.map:add(self.sectiontype)
894 for k,v in pairs(self.children) do
896 self.map:set(section, v.option, v.default)
900 for k,v in pairs(self.defaults) do
901 self.map:set(section, k, v)
905 self.map.proceed = true
911 SimpleSection = class(AbstractSection)
913 function SimpleSection.__init__(self, form, ...)
914 AbstractSection.__init__(self, form, nil, ...)
915 self.template = "cbi/nullsection"
919 Table = class(AbstractSection)
921 function Table.__init__(self, form, data, ...)
922 local datasource = {}
924 datasource.config = "table"
925 self.data = data or {}
927 datasource.formvalue = Map.formvalue
928 datasource.formvaluetable = Map.formvaluetable
929 datasource.readinput = true
931 function datasource.get(self, section, option)
932 return tself.data[section] and tself.data[section][option]
935 function datasource.submitstate(self)
936 return Map.formvalue(self, "cbi.submit")
939 function datasource.del(...)
943 function datasource.get_scheme()
947 AbstractSection.__init__(self, datasource, "table", ...)
948 self.template = "cbi/tblsection"
949 self.rowcolors = true
950 self.anonymous = true
953 function Table.parse(self, readinput)
954 self.map.readinput = (readinput ~= false)
955 for i, k in ipairs(self:cfgsections()) do
956 if self.map:submitstate() then
962 function Table.cfgsections(self)
965 for i, v in luci.util.kspairs(self.data) do
966 table.insert(sections, i)
972 function Table.update(self, data)
979 NamedSection - A fixed configuration section defined by its name
981 NamedSection = class(AbstractSection)
983 function NamedSection.__init__(self, map, section, stype, ...)
984 AbstractSection.__init__(self, map, stype, ...)
987 self.addremove = false
988 self.template = "cbi/nsection"
989 self.section = section
992 function NamedSection.parse(self, novld)
993 local s = self.section
994 local active = self:cfgvalue(s)
996 if self.addremove then
997 local path = self.config.."."..s
998 if active then -- Remove the section
999 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1003 else -- Create and apply default values
1004 if self.map:formvalue("cbi.cns."..path) then
1012 AbstractSection.parse_dynamic(self, s)
1013 if self.map:submitstate() then
1016 AbstractSection.parse_optionals(self, s)
1018 if self.changed then
1026 TypedSection - A (set of) configuration section(s) defined by the type
1027 addremove: Defines whether the user can add/remove sections of this type
1028 anonymous: Allow creating anonymous sections
1029 validate: a validation function returning nil if the section is invalid
1031 TypedSection = class(AbstractSection)
1033 function TypedSection.__init__(self, map, type, ...)
1034 AbstractSection.__init__(self, map, type, ...)
1036 self.template = "cbi/tsection"
1038 self.anonymous = false
1041 -- Return all matching UCI sections for this TypedSection
1042 function TypedSection.cfgsections(self)
1044 self.map.uci:foreach(self.map.config, self.sectiontype,
1046 if self:checkscope(section[".name"]) then
1047 table.insert(sections, section[".name"])
1054 -- Limits scope to sections that have certain option => value pairs
1055 function TypedSection.depends(self, option, value)
1056 table.insert(self.deps, {option=option, value=value})
1059 function TypedSection.parse(self, novld)
1060 if self.addremove then
1062 local crval = REMOVE_PREFIX .. self.config
1063 local name = self.map:formvaluetable(crval)
1064 for k,v in pairs(name) do
1065 if k:sub(-2) == ".x" then
1066 k = k:sub(1, #k - 2)
1068 if self:cfgvalue(k) and self:checkscope(k) then
1075 for i, k in ipairs(self:cfgsections()) do
1076 AbstractSection.parse_dynamic(self, k)
1077 if self.map:submitstate() then
1078 Node.parse(self, k, novld)
1080 AbstractSection.parse_optionals(self, k)
1083 if self.addremove then
1086 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1087 local name = self.map:formvalue(crval)
1088 if self.anonymous then
1090 created = self:create()
1094 -- Ignore if it already exists
1095 if self:cfgvalue(name) then
1099 name = self:checkscope(name)
1102 self.err_invalid = true
1105 if name and #name > 0 then
1106 created = self:create(name) and name
1108 self.invalid_cts = true
1115 AbstractSection.parse_optionals(self, created)
1119 if created or self.changed then
1124 -- Verifies scope of sections
1125 function TypedSection.checkscope(self, section)
1126 -- Check if we are not excluded
1127 if self.filter and not self:filter(section) then
1131 -- Check if at least one dependency is met
1132 if #self.deps > 0 and self:cfgvalue(section) then
1135 for k, v in ipairs(self.deps) do
1136 if self:cfgvalue(section)[v.option] == v.value then
1146 return self:validate(section)
1150 -- Dummy validate function
1151 function TypedSection.validate(self, section)
1157 AbstractValue - An abstract Value Type
1158 null: Value can be empty
1159 valid: A function returning the value if it is valid otherwise nil
1160 depends: A table of option => value pairs of which one must be true
1161 default: The default value
1162 size: The size of the input fields
1163 rmempty: Unset value if empty
1164 optional: This value is optional (see AbstractSection.optionals)
1166 AbstractValue = class(Node)
1168 function AbstractValue.__init__(self, map, section, option, ...)
1169 Node.__init__(self, ...)
1170 self.section = section
1171 self.option = option
1173 self.config = map.config
1174 self.tag_invalid = {}
1175 self.tag_missing = {}
1176 self.tag_reqerror = {}
1180 --self.cast = "string"
1182 self.track_missing = false
1186 self.optional = false
1189 function AbstractValue.prepare(self)
1190 self.cast = self.cast or "string"
1193 -- Add a dependencie to another section field
1194 function AbstractValue.depends(self, field, value)
1196 if type(field) == "string" then
1203 table.insert(self.deps, {deps=deps, add=""})
1206 -- Generates the unique CBID
1207 function AbstractValue.cbid(self, section)
1208 return "cbid."..self.map.config.."."..section.."."..self.option
1211 -- Return whether this object should be created
1212 function AbstractValue.formcreated(self, section)
1213 local key = "cbi.opt."..self.config.."."..section
1214 return (self.map:formvalue(key) == self.option)
1217 -- Returns the formvalue for this object
1218 function AbstractValue.formvalue(self, section)
1219 return self.map:formvalue(self:cbid(section))
1222 function AbstractValue.additional(self, value)
1223 self.optional = value
1226 function AbstractValue.mandatory(self, value)
1227 self.rmempty = not value
1230 function AbstractValue.parse(self, section, novld)
1231 local fvalue = self:formvalue(section)
1232 local cvalue = self:cfgvalue(section)
1234 -- If favlue and cvalue are both tables and have the same content
1235 -- make them identical
1236 if type(fvalue) == "table" and type(cvalue) == "table" then
1237 local equal = #fvalue == #cvalue
1240 if cvalue[i] ~= fvalue[i] then
1250 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1251 fvalue = self:transform(self:validate(fvalue, section))
1252 if not fvalue and not novld then
1254 self.error[section] = "invalid"
1256 self.error = { [section] = "invalid" }
1258 if self.section.error then
1259 table.insert(self.section.error[section], "invalid")
1261 self.section.error = {[section] = {"invalid"}}
1263 self.map.save = false
1265 if fvalue and not (fvalue == cvalue) then
1266 if self:write(section, fvalue) then
1268 self.section.changed = true
1269 --luci.util.append(self.map.events, self.events)
1272 else -- Unset the UCI or error
1273 if self.rmempty or self.optional then
1274 if self:remove(section) then
1276 self.section.changed = true
1277 --luci.util.append(self.map.events, self.events)
1279 elseif cvalue ~= fvalue and not novld then
1280 self:write(section, fvalue or "")
1282 self.error[section] = "missing"
1284 self.error = { [section] = "missing" }
1286 self.map.save = false
1291 -- Render if this value exists or if it is mandatory
1292 function AbstractValue.render(self, s, scope)
1293 if not self.optional or next(self.section.tabs) or self:cfgvalue(s) or self:formcreated(s) then
1296 scope.cbid = self:cbid(s)
1297 scope.striptags = luci.util.striptags
1298 scope.pcdata = luci.util.pcdata
1300 scope.ifattr = function(cond,key,val)
1302 return string.format(
1303 ' %s="%s"', tostring(key),
1304 luci.util.pcdata(tostring( val
1306 or (type(self[key]) ~= "function" and self[key])
1314 scope.attr = function(...)
1315 return scope.ifattr( true, ... )
1318 Node.render(self, scope)
1322 -- Return the UCI value of this object
1323 function AbstractValue.cfgvalue(self, section)
1324 local value = (self.error and self.error[section] == "invalid")
1325 and self:formvalue(section) or self.map:get(section, self.option)
1328 elseif not self.cast or self.cast == type(value) then
1330 elseif self.cast == "string" then
1331 if type(value) == "table" then
1334 elseif self.cast == "table" then
1335 return luci.util.split(value, "%s+", nil, true)
1339 -- Validate the form value
1340 function AbstractValue.validate(self, value)
1341 if self.datatype and value and datatypes[self.datatype] then
1342 if type(value) == "table" then
1344 for _, v in ipairs(value) do
1345 if v and #v > 0 and not datatypes[self.datatype](v) then
1350 if not datatypes[self.datatype](value) then
1358 AbstractValue.transform = AbstractValue.validate
1362 function AbstractValue.write(self, section, value)
1363 return self.map:set(section, self.option, value)
1367 function AbstractValue.remove(self, section)
1368 return self.map:del(section, self.option)
1375 Value - A one-line value
1376 maxlength: The maximum length
1378 Value = class(AbstractValue)
1380 function Value.__init__(self, ...)
1381 AbstractValue.__init__(self, ...)
1382 self.template = "cbi/value"
1387 function Value.value(self, key, val)
1389 table.insert(self.keylist, tostring(key))
1390 table.insert(self.vallist, tostring(val))
1394 -- DummyValue - This does nothing except being there
1395 DummyValue = class(AbstractValue)
1397 function DummyValue.__init__(self, ...)
1398 AbstractValue.__init__(self, ...)
1399 self.template = "cbi/dvalue"
1403 function DummyValue.cfgvalue(self, section)
1406 if type(self.value) == "function" then
1407 value = self:value(section)
1412 value = AbstractValue.cfgvalue(self, section)
1417 function DummyValue.parse(self)
1423 Flag - A flag being enabled or disabled
1425 Flag = class(AbstractValue)
1427 function Flag.__init__(self, ...)
1428 AbstractValue.__init__(self, ...)
1429 self.template = "cbi/fvalue"
1435 -- A flag can only have two states: set or unset
1436 function Flag.parse(self, section)
1437 local fvalue = self:formvalue(section)
1440 fvalue = self.enabled
1442 fvalue = self.disabled
1445 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1446 if not(fvalue == self:cfgvalue(section)) then
1447 self:write(section, fvalue)
1450 self:remove(section)
1457 ListValue - A one-line value predefined in a list
1458 widget: The widget that will be used (select, radio)
1460 ListValue = class(AbstractValue)
1462 function ListValue.__init__(self, ...)
1463 AbstractValue.__init__(self, ...)
1464 self.template = "cbi/lvalue"
1469 self.widget = "select"
1472 function ListValue.value(self, key, val, ...)
1473 if luci.util.contains(self.keylist, key) then
1478 table.insert(self.keylist, tostring(key))
1479 table.insert(self.vallist, tostring(val))
1481 for i, deps in ipairs({...}) do
1482 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1486 function ListValue.validate(self, val)
1487 if luci.util.contains(self.keylist, val) then
1497 MultiValue - Multiple delimited values
1498 widget: The widget that will be used (select, checkbox)
1499 delimiter: The delimiter that will separate the values (default: " ")
1501 MultiValue = class(AbstractValue)
1503 function MultiValue.__init__(self, ...)
1504 AbstractValue.__init__(self, ...)
1505 self.template = "cbi/mvalue"
1510 self.widget = "checkbox"
1511 self.delimiter = " "
1514 function MultiValue.render(self, ...)
1515 if self.widget == "select" and not self.size then
1516 self.size = #self.vallist
1519 AbstractValue.render(self, ...)
1522 function MultiValue.value(self, key, val)
1523 if luci.util.contains(self.keylist, key) then
1528 table.insert(self.keylist, tostring(key))
1529 table.insert(self.vallist, tostring(val))
1532 function MultiValue.valuelist(self, section)
1533 local val = self:cfgvalue(section)
1535 if not(type(val) == "string") then
1539 return luci.util.split(val, self.delimiter)
1542 function MultiValue.validate(self, val)
1543 val = (type(val) == "table") and val or {val}
1547 for i, value in ipairs(val) do
1548 if luci.util.contains(self.keylist, value) then
1549 result = result and (result .. self.delimiter .. value) or value
1557 StaticList = class(MultiValue)
1559 function StaticList.__init__(self, ...)
1560 MultiValue.__init__(self, ...)
1562 self.valuelist = self.cfgvalue
1564 if not self.override_scheme
1565 and self.map:get_scheme(self.section.sectiontype, self.option) then
1566 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1567 if self.value and vs.values and not self.override_values then
1568 for k, v in pairs(vs.values) do
1575 function StaticList.validate(self, value)
1576 value = (type(value) == "table") and value or {value}
1579 for i, v in ipairs(value) do
1580 if luci.util.contains(self.keylist, v) then
1581 table.insert(valid, v)
1588 DynamicList = class(AbstractValue)
1590 function DynamicList.__init__(self, ...)
1591 AbstractValue.__init__(self, ...)
1592 self.template = "cbi/dynlist"
1598 function DynamicList.value(self, key, val)
1600 table.insert(self.keylist, tostring(key))
1601 table.insert(self.vallist, tostring(val))
1604 function DynamicList.write(self, ...)
1605 self.map.proceed = true
1606 return AbstractValue.write(self, ...)
1609 function DynamicList.formvalue(self, section)
1610 local value = AbstractValue.formvalue(self, section)
1611 value = (type(value) == "table") and value or {value}
1614 for i, v in ipairs(value) do
1616 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1617 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1618 table.insert(valid, v)
1627 TextValue - A multi-line value
1630 TextValue = class(AbstractValue)
1632 function TextValue.__init__(self, ...)
1633 AbstractValue.__init__(self, ...)
1634 self.template = "cbi/tvalue"
1640 Button = class(AbstractValue)
1642 function Button.__init__(self, ...)
1643 AbstractValue.__init__(self, ...)
1644 self.template = "cbi/button"
1645 self.inputstyle = nil
1650 FileUpload = class(AbstractValue)
1652 function FileUpload.__init__(self, ...)
1653 AbstractValue.__init__(self, ...)
1654 self.template = "cbi/upload"
1655 if not self.map.upload_fields then
1656 self.map.upload_fields = { self }
1658 self.map.upload_fields[#self.map.upload_fields+1] = self
1662 function FileUpload.formcreated(self, section)
1663 return AbstractValue.formcreated(self, section) or
1664 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1665 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1668 function FileUpload.cfgvalue(self, section)
1669 local val = AbstractValue.cfgvalue(self, section)
1670 if val and fs.access(val) then
1676 function FileUpload.formvalue(self, section)
1677 local val = AbstractValue.formvalue(self, section)
1679 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1680 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1690 function FileUpload.remove(self, section)
1691 local val = AbstractValue.formvalue(self, section)
1692 if val and fs.access(val) then fs.unlink(val) end
1693 return AbstractValue.remove(self, section)
1697 FileBrowser = class(AbstractValue)
1699 function FileBrowser.__init__(self, ...)
1700 AbstractValue.__init__(self, ...)
1701 self.template = "cbi/browser"