libs/cbi: allow underscores in AbstractSection.create()
[project/luci.git] / libs / cbi / luasrc / cbi.lua
1 --[[
2 LuCI - Configuration Bind Interface
3
4 Description:
5 Offers an interface for binding configuration values to certain
6 data types. Supports value and range validation and basic dependencies.
7
8 FileId:
9 $Id$
10
11 License:
12 Copyright 2008 Steven Barth <steven@midlink.org>
13
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
17
18 http://www.apache.org/licenses/LICENSE-2.0
19
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.
25
26 ]]--
27 module("luci.cbi", package.seeall)
28
29 require("luci.template")
30 local util = require("luci.util")
31 require("luci.http")
32 require("luci.uvl")
33
34
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
40
41 FORM_NODATA = 0
42 FORM_PROCEED = 0
43 FORM_VALID = 1
44 FORM_DONE = 1
45 FORM_INVALID = -1
46 FORM_CHANGED = 2
47 FORM_SKIP = 4
48
49 AUTO = true
50
51 CREATE_PREFIX = "cbi.cts."
52 REMOVE_PREFIX = "cbi.rts."
53
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")
59 require("luci.util")
60
61 local upldir = "/lib/uci/upload/"
62 local cbidir = luci.util.libpath() .. "/model/cbi/"
63 local func, err
64
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")
71 else
72 func, err = nil, "Model '" .. cbimap .. "' not found!"
73 end
74
75 assert(func, err)
76
77 luci.i18n.loadc("cbi")
78 luci.i18n.loadc("uvl")
79
80 local env = {
81 translate=i18n.translate,
82 translatef=i18n.translatef,
83 arg={...}
84 }
85
86 setfenv(func, setmetatable(env, {__index =
87 function(tbl, key)
88 return rawget(tbl, key) or _M[key] or _G[key]
89 end}))
90
91 local maps = { func() }
92 local uploads = { }
93 local has_upload = false
94
95 for i, map in ipairs(maps) do
96 if not instanceof(map, Node) then
97 error("CBI map returns no valid map object!")
98 return nil
99 else
100 map:prepare()
101 if map.upload_fields then
102 has_upload = true
103 for _, field in ipairs(map.upload_fields) do
104 uploads[
105 field.config .. '.' ..
106 field.section.sectiontype .. '.' ..
107 field.option
108 ] = true
109 end
110 end
111 end
112 end
113
114 if has_upload then
115 local uci = luci.model.uci.cursor()
116 local prm = luci.http.context.request.message.params
117 local fd, cbid
118
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%.([^%.]+)%.([^%.]+)%.([^%.]+)"
125 )()
126
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")
132 if fd then
133 cbid = field.name
134 prm[cbid] = path
135 end
136 end
137 end
138 end
139
140 if field.name == cbid and fd then
141 fd:write(chunk)
142 end
143
144 if eof and fd then
145 fd:close()
146 fd = nil
147 cbid = nil
148 end
149 end
150 )
151 end
152
153 return maps
154 end
155
156 local function _uvl_validate_section(node, name)
157 local co = node.map:get()
158
159 luci.uvl.STRICT_UNKNOWN_OPTIONS = false
160 luci.uvl.STRICT_UNKNOWN_SECTIONS = false
161
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
166 else
167 node.fields[e.option].error = { [name] = e }
168 end
169 elseif e.childs then
170 for _, c in ipairs(e.childs) do tag_fields(c) end
171 end
172 end
173
174 local function tag_section(e)
175 local s = { }
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() )
179 else
180 table.insert( s, c:string() )
181 end
182 end
183 if #s > 0 then
184 if node.error then
185 node.error[name] = s
186 else
187 node.error = { [name] = s }
188 end
189 end
190 end
191
192 local stat, err = node.map.validator:validate_section(node.config, name, co)
193 if err then
194 node.map.save = false
195 tag_fields(err)
196 tag_section(err)
197 end
198
199 end
200
201 local function _uvl_strip_remote_dependencies(deps)
202 local clean = {}
203
204 for k, v in pairs(deps) do
205 k = k:gsub("%$config%.%$section%.", "")
206 if k:match("^[%w_]+$") and type(v) == "string" then
207 clean[k] = v
208 end
209 end
210
211 return clean
212 end
213
214
215 -- Node pseudo abstract class
216 Node = class()
217
218 function Node.__init__(self, title, description)
219 self.children = {}
220 self.title = title or ""
221 self.description = description or ""
222 self.template = "cbi/node"
223 end
224
225 -- i18n helper
226 function Node._i18n(self, config, section, option, title, description)
227
228 -- i18n loaded?
229 if type(luci.i18n) == "table" then
230
231 local key = config and config:gsub("[^%w]+", "") or ""
232
233 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
234 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
235
236 self.title = title or luci.i18n.translate( key, option or section or config )
237 self.description = description or luci.i18n.translate( key .. "_desc", "" )
238 end
239 end
240
241 -- Prepare nodes
242 function Node.prepare(self, ...)
243 for k, child in ipairs(self.children) do
244 child:prepare(...)
245 end
246 end
247
248 -- Append child nodes
249 function Node.append(self, obj)
250 table.insert(self.children, obj)
251 end
252
253 -- Parse this node and its children
254 function Node.parse(self, ...)
255 for k, child in ipairs(self.children) do
256 child:parse(...)
257 end
258 end
259
260 -- Render this node
261 function Node.render(self, scope)
262 scope = scope or {}
263 scope.self = self
264
265 luci.template.render(self.template, scope)
266 end
267
268 -- Render the children
269 function Node.render_children(self, ...)
270 for k, node in ipairs(self.children) do
271 node:render(...)
272 end
273 end
274
275
276 --[[
277 A simple template element
278 ]]--
279 Template = class(Node)
280
281 function Template.__init__(self, template)
282 Node.__init__(self)
283 self.template = template
284 end
285
286 function Template.render(self)
287 luci.template.render(self.template, {self=self})
288 end
289
290 function Template.parse(self, readinput)
291 self.readinput = (readinput ~= false)
292 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
293 end
294
295
296 --[[
297 Map - A map describing a configuration file
298 ]]--
299 Map = class(Node)
300
301 function Map.__init__(self, config, ...)
302 Node.__init__(self, ...)
303 Node._i18n(self, config, nil, nil, ...)
304
305 self.config = config
306 self.parsechain = {self.config}
307 self.template = "cbi/map"
308 self.apply_on_parse = nil
309 self.readinput = true
310 self.proceed = false
311 self.flow = {}
312
313 self.uci = uci.cursor()
314 self.save = true
315
316 self.changed = false
317
318 if not self.uci:load(self.config) then
319 error("Unable to read UCI data: " .. self.config)
320 end
321
322 self.validator = luci.uvl.UVL()
323 self.scheme = self.validator:get_scheme(self.config)
324
325 end
326
327 function Map.formvalue(self, key)
328 return self.readinput and luci.http.formvalue(key)
329 end
330
331 function Map.formvaluetable(self, key)
332 return self.readinput and luci.http.formvaluetable(key) or {}
333 end
334
335 function Map.get_scheme(self, sectiontype, option)
336 if not option then
337 return self.scheme and self.scheme.sections[sectiontype]
338 else
339 return self.scheme and self.scheme.variables[sectiontype]
340 and self.scheme.variables[sectiontype][option]
341 end
342 end
343
344 function Map.submitstate(self)
345 return self:formvalue("cbi.submit")
346 end
347
348 -- Chain foreign config
349 function Map.chain(self, config)
350 table.insert(self.parsechain, config)
351 end
352
353 function Map.state_handler(self, state)
354 return state
355 end
356
357 -- Use optimized UCI writing
358 function Map.parse(self, readinput, ...)
359 self.readinput = (readinput ~= false)
360
361 if self:formvalue("cbi.skip") then
362 self.state = FORM_SKIP
363 return self:state_handler(self.state)
364 end
365
366 Node.parse(self, ...)
367
368 if self.save then
369 for i, config in ipairs(self.parsechain) do
370 self.uci:save(config)
371 end
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)
375
376 -- Refresh data because commit changes section names
377 self.uci:load(config)
378 end
379 if self.apply_on_parse then
380 self.uci:apply(self.parsechain)
381 else
382 self._apply = function()
383 local cmd = self.uci:apply(self.parsechain, true)
384 return io.popen(cmd)
385 end
386 end
387
388 -- Reparse sections
389 Node.parse(self, true)
390
391 end
392 for i, config in ipairs(self.parsechain) do
393 self.uci:unload(config)
394 end
395 if type(self.commit_handler) == "function" then
396 self:commit_handler(self:submitstate())
397 end
398 end
399
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
405 else
406 self.state = self.changed and FORM_CHANGED or FORM_VALID
407 end
408 else
409 self.state = FORM_NODATA
410 end
411
412 return self:state_handler(self.state)
413 end
414
415 function Map.render(self, ...)
416 Node.render(self, ...)
417 if self._apply then
418 local fp = self._apply()
419 fp:read("*a")
420 fp:close()
421 end
422 end
423
424 -- Creates a child section
425 function Map.section(self, class, ...)
426 if instanceof(class, AbstractSection) then
427 local obj = class(self, ...)
428 self:append(obj)
429 return obj
430 else
431 error("class must be a descendent of AbstractSection")
432 end
433 end
434
435 -- UCI add
436 function Map.add(self, sectiontype)
437 return self.uci:add(self.config, sectiontype)
438 end
439
440 -- UCI set
441 function Map.set(self, section, option, value)
442 if option then
443 return self.uci:set(self.config, section, option, value)
444 else
445 return self.uci:set(self.config, section, value)
446 end
447 end
448
449 -- UCI del
450 function Map.del(self, section, option)
451 if option then
452 return self.uci:delete(self.config, section, option)
453 else
454 return self.uci:delete(self.config, section)
455 end
456 end
457
458 -- UCI get
459 function Map.get(self, section, option)
460 if not section then
461 return self.uci:get_all(self.config)
462 elseif option then
463 return self.uci:get(self.config, section, option)
464 else
465 return self.uci:get_all(self.config, section)
466 end
467 end
468
469 --[[
470 Compound - Container
471 ]]--
472 Compound = class(Node)
473
474 function Compound.__init__(self, ...)
475 Node.__init__(self)
476 self.template = "cbi/compound"
477 self.children = {...}
478 end
479
480 function Compound.populate_delegator(self, delegator)
481 for _, v in ipairs(self.children) do
482 v.delegator = delegator
483 end
484 end
485
486 function Compound.parse(self, ...)
487 local cstate, state = 0
488
489 for k, child in ipairs(self.children) do
490 cstate = child:parse(...)
491 state = (not state or cstate < state) and cstate or state
492 end
493
494 return state
495 end
496
497
498 --[[
499 Delegator - Node controller
500 ]]--
501 Delegator = class(Node)
502 function Delegator.__init__(self, ...)
503 Node.__init__(self, ...)
504 self.nodes = {}
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"
512 end
513
514 function Delegator.set(self, name, node)
515 if type(node) == "table" and getmetatable(node) == nil then
516 node = Compound(unpack(node))
517 end
518 assert(type(node) == "function" or instanceof(node, Compound), "Invalid")
519 assert(not self.nodes[name], "Duplicate entry")
520
521 self.nodes[name] = node
522 end
523
524 function Delegator.add(self, name, node)
525 node = self:set(name, node)
526 self.defaultpath[#self.defaultpath+1] = name
527 end
528
529 function Delegator.insert_after(self, name, after)
530 local n = #self.chain
531 for k, v in ipairs(self.chain) do
532 if v == state then
533 n = k + 1
534 break
535 end
536 end
537 table.insert(self.chain, n, name)
538 end
539
540 function Delegator.set_route(self, ...)
541 local n, chain, route = 0, self.chain, {...}
542 for i = 1, #chain do
543 if chain[i] == self.current then
544 n = i
545 break
546 end
547 end
548 for i = 1, #route do
549 n = n + 1
550 chain[n] = route[i]
551 end
552 for i = n + 1, #chain do
553 chain[i] = nil
554 end
555 end
556
557 function Delegator.get(self, name)
558 return self.nodes[name]
559 end
560
561 function Delegator.parse(self, ...)
562 local newcurrent
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")
567
568 local stat = FORM_DONE
569 if type(self.active) ~= "function" then
570 self.active:populate_delegator(self)
571 stat = self.active:parse()
572 else
573 self:active()
574 end
575
576 if stat > FORM_PROCEED then
577 if Map.formvalue(self, "cbi.delg.back") then
578 newcurrent = self:get_prev(self.current)
579 else
580 newcurrent = self:get_next(self.current)
581 end
582 elseif stat < FORM_PROCEED then
583 return stat
584 end
585
586
587 if not Map.formvalue(self, "cbi.submit") then
588 return FORM_NODATA
589 elseif not newcurrent or not self:get(newcurrent) then
590 return FORM_DONE
591 else
592 self.current = newcurrent
593 self.active = self:get(self.current)
594 if type(self.active) ~= "function" then
595 self.active:parse(false)
596 return FROM_PROCEED
597 else
598 return self:parse(...)
599 end
600 end
601 end
602
603 function Delegator.get_next(self, state)
604 for k, v in ipairs(self.chain) do
605 if v == state then
606 return self.chain[k+1]
607 end
608 end
609 end
610
611 function Delegator.get_prev(self, state)
612 for k, v in ipairs(self.chain) do
613 if v == state then
614 return self.chain[k-1]
615 end
616 end
617 end
618
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}
622 end
623
624 function Delegator.get_active(self)
625 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
626 end
627
628 --[[
629 Page - A simple node
630 ]]--
631
632 Page = class(Node)
633 Page.__init__ = Node.__init__
634 Page.parse = function() end
635
636
637 --[[
638 SimpleForm - A Simple non-UCI form
639 ]]--
640 SimpleForm = class(Node)
641
642 function SimpleForm.__init__(self, config, title, description, data)
643 Node.__init__(self, title, description)
644 self.config = config
645 self.data = data or {}
646 self.template = "cbi/simpleform"
647 self.dorender = true
648 self.pageaction = false
649 self.readinput = true
650 end
651
652 SimpleForm.formvalue = Map.formvalue
653 SimpleForm.formvaluetable = Map.formvaluetable
654
655 function SimpleForm.parse(self, readinput, ...)
656 self.readinput = (readinput ~= false)
657
658 if self:formvalue("cbi.skip") then
659 return FORM_SKIP
660 end
661
662 if self:submitstate() then
663 Node.parse(self, 1, ...)
664 end
665
666 local valid = true
667 for k, j in ipairs(self.children) do
668 for i, v in ipairs(j.children) do
669 valid = valid
670 and (not v.tag_missing or not v.tag_missing[1])
671 and (not v.tag_invalid or not v.tag_invalid[1])
672 and (not v.error)
673 end
674 end
675
676 local state =
677 not self:submitstate() and FORM_NODATA
678 or valid and FORM_VALID
679 or FORM_INVALID
680
681 self.dorender = not self.handle
682 if self.handle then
683 local nrender, nstate = self:handle(state, self.data)
684 self.dorender = self.dorender or (nrender ~= false)
685 state = nstate or state
686 end
687 return state
688 end
689
690 function SimpleForm.render(self, ...)
691 if self.dorender then
692 Node.render(self, ...)
693 end
694 end
695
696 function SimpleForm.submitstate(self)
697 return self:formvalue("cbi.submit")
698 end
699
700 function SimpleForm.section(self, class, ...)
701 if instanceof(class, AbstractSection) then
702 local obj = class(self, ...)
703 self:append(obj)
704 return obj
705 else
706 error("class must be a descendent of AbstractSection")
707 end
708 end
709
710 -- Creates a child field
711 function SimpleForm.field(self, class, ...)
712 local section
713 for k, v in ipairs(self.children) do
714 if instanceof(v, SimpleSection) then
715 section = v
716 break
717 end
718 end
719 if not section then
720 section = self:section(SimpleSection)
721 end
722
723 if instanceof(class, AbstractValue) then
724 local obj = class(self, section, ...)
725 obj.track_missing = true
726 section:append(obj)
727 return obj
728 else
729 error("class must be a descendent of AbstractValue")
730 end
731 end
732
733 function SimpleForm.set(self, section, option, value)
734 self.data[option] = value
735 end
736
737
738 function SimpleForm.del(self, section, option)
739 self.data[option] = nil
740 end
741
742
743 function SimpleForm.get(self, section, option)
744 return self.data[option]
745 end
746
747
748 function SimpleForm.get_scheme()
749 return nil
750 end
751
752
753 Form = class(SimpleForm)
754
755 function Form.__init__(self, ...)
756 SimpleForm.__init__(self, ...)
757 self.embedded = true
758 end
759
760
761 --[[
762 AbstractSection
763 ]]--
764 AbstractSection = class(Node)
765
766 function AbstractSection.__init__(self, map, sectiontype, ...)
767 Node.__init__(self, ...)
768 self.sectiontype = sectiontype
769 self.map = map
770 self.config = map.config
771 self.optionals = {}
772 self.defaults = {}
773 self.fields = {}
774 self.tag_error = {}
775 self.tag_invalid = {}
776 self.tag_deperror = {}
777 self.changed = false
778
779 self.optional = true
780 self.addremove = false
781 self.dynamic = false
782 end
783
784 -- Define a tab for the section
785 function AbstractSection.tab(self, tab, title, desc)
786 self.tabs = self.tabs or { }
787 self.tab_names = self.tab_names or { }
788
789 self.tab_names[#self.tab_names+1] = tab
790 self.tabs[tab] = {
791 title = title,
792 description = desc,
793 childs = { }
794 }
795 end
796
797 -- Appends a new option
798 function AbstractSection.option(self, class, option, ...)
799 -- Autodetect from UVL
800 if class == true and self.map:get_scheme(self.sectiontype, option) then
801 local vs = self.map:get_scheme(self.sectiontype, option)
802 if vs.type == "boolean" then
803 class = Flag
804 elseif vs.type == "list" then
805 class = DynamicList
806 elseif vs.type == "enum" or vs.type == "reference" then
807 class = ListValue
808 else
809 class = Value
810 end
811 end
812
813 if instanceof(class, AbstractValue) then
814 local obj = class(self.map, self, option, ...)
815
816 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
817
818 self:append(obj)
819 self.fields[option] = obj
820 return obj
821 elseif class == true then
822 error("No valid class was given and autodetection failed.")
823 else
824 error("class must be a descendant of AbstractValue")
825 end
826 end
827
828 -- Appends a new tabbed option
829 function AbstractSection.taboption(self, tab, ...)
830
831 assert(tab and self.tabs and self.tabs[tab],
832 "Cannot assign option to not existing tab %q" % tostring(tab))
833
834 local l = self.tabs[tab].childs
835 local o = AbstractSection.option(self, ...)
836
837 if o then l[#l+1] = o end
838
839 return o
840 end
841
842 -- Render a single tab
843 function AbstractSection.render_tab(self, tab, ...)
844
845 assert(tab and self.tabs and self.tabs[tab],
846 "Cannot render not existing tab %q" % tostring(tab))
847
848 for _, node in ipairs(self.tabs[tab].childs) do
849 node:render(...)
850 end
851 end
852
853 -- Parse optional options
854 function AbstractSection.parse_optionals(self, section)
855 if not self.optional then
856 return
857 end
858
859 self.optionals[section] = {}
860
861 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
862 for k,v in ipairs(self.children) do
863 if v.optional and not v:cfgvalue(section) then
864 if field == v.option then
865 field = nil
866 self.map.proceed = true
867 else
868 table.insert(self.optionals[section], v)
869 end
870 end
871 end
872
873 if field and #field > 0 and self.dynamic then
874 self:add_dynamic(field)
875 end
876 end
877
878 -- Add a dynamic option
879 function AbstractSection.add_dynamic(self, field, optional)
880 local o = self:option(Value, field, field)
881 o.optional = optional
882 end
883
884 -- Parse all dynamic options
885 function AbstractSection.parse_dynamic(self, section)
886 if not self.dynamic then
887 return
888 end
889
890 local arr = luci.util.clone(self:cfgvalue(section))
891 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
892 for k, v in pairs(form) do
893 arr[k] = v
894 end
895
896 for key,val in pairs(arr) do
897 local create = true
898
899 for i,c in ipairs(self.children) do
900 if c.option == key then
901 create = false
902 end
903 end
904
905 if create and key:sub(1, 1) ~= "." then
906 self.map.proceed = true
907 self:add_dynamic(key, true)
908 end
909 end
910 end
911
912 -- Returns the section's UCI table
913 function AbstractSection.cfgvalue(self, section)
914 return self.map:get(section)
915 end
916
917 -- Push events
918 function AbstractSection.push_events(self)
919 --luci.util.append(self.map.events, self.events)
920 self.map.changed = true
921 end
922
923 -- Removes the section
924 function AbstractSection.remove(self, section)
925 self.map.proceed = true
926 return self.map:del(section)
927 end
928
929 -- Creates the section
930 function AbstractSection.create(self, section)
931 local stat
932
933 if section then
934 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
935 else
936 section = self.map:add(self.sectiontype)
937 stat = section
938 end
939
940 if stat then
941 for k,v in pairs(self.children) do
942 if v.default then
943 self.map:set(section, v.option, v.default)
944 end
945 end
946
947 for k,v in pairs(self.defaults) do
948 self.map:set(section, k, v)
949 end
950 end
951
952 self.map.proceed = true
953
954 return stat
955 end
956
957
958 SimpleSection = class(AbstractSection)
959
960 function SimpleSection.__init__(self, form, ...)
961 AbstractSection.__init__(self, form, nil, ...)
962 self.template = "cbi/nullsection"
963 end
964
965
966 Table = class(AbstractSection)
967
968 function Table.__init__(self, form, data, ...)
969 local datasource = {}
970 local tself = self
971 datasource.config = "table"
972 self.data = data or {}
973
974 datasource.formvalue = Map.formvalue
975 datasource.formvaluetable = Map.formvaluetable
976 datasource.readinput = true
977
978 function datasource.get(self, section, option)
979 return tself.data[section] and tself.data[section][option]
980 end
981
982 function datasource.submitstate(self)
983 return Map.formvalue(self, "cbi.submit")
984 end
985
986 function datasource.del(...)
987 return true
988 end
989
990 function datasource.get_scheme()
991 return nil
992 end
993
994 AbstractSection.__init__(self, datasource, "table", ...)
995 self.template = "cbi/tblsection"
996 self.rowcolors = true
997 self.anonymous = true
998 end
999
1000 function Table.parse(self, readinput)
1001 self.map.readinput = (readinput ~= false)
1002 for i, k in ipairs(self:cfgsections()) do
1003 if self.map:submitstate() then
1004 Node.parse(self, k)
1005 end
1006 end
1007 end
1008
1009 function Table.cfgsections(self)
1010 local sections = {}
1011
1012 for i, v in luci.util.kspairs(self.data) do
1013 table.insert(sections, i)
1014 end
1015
1016 return sections
1017 end
1018
1019 function Table.update(self, data)
1020 self.data = data
1021 end
1022
1023
1024
1025 --[[
1026 NamedSection - A fixed configuration section defined by its name
1027 ]]--
1028 NamedSection = class(AbstractSection)
1029
1030 function NamedSection.__init__(self, map, section, stype, ...)
1031 AbstractSection.__init__(self, map, stype, ...)
1032 Node._i18n(self, map.config, section, nil, ...)
1033
1034 -- Defaults
1035 self.addremove = false
1036
1037 -- Use defaults from UVL
1038 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1039 local vs = self.map:get_scheme(self.sectiontype)
1040 self.addremove = not vs.unique and not vs.required
1041 self.dynamic = vs.dynamic
1042 self.title = self.title or vs.title
1043 self.description = self.description or vs.descr
1044 end
1045
1046 self.template = "cbi/nsection"
1047 self.section = section
1048 end
1049
1050 function NamedSection.parse(self, novld)
1051 local s = self.section
1052 local active = self:cfgvalue(s)
1053
1054 if self.addremove then
1055 local path = self.config.."."..s
1056 if active then -- Remove the section
1057 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1058 self:push_events()
1059 return
1060 end
1061 else -- Create and apply default values
1062 if self.map:formvalue("cbi.cns."..path) then
1063 self:create(s)
1064 return
1065 end
1066 end
1067 end
1068
1069 if active then
1070 AbstractSection.parse_dynamic(self, s)
1071 if self.map:submitstate() then
1072 Node.parse(self, s)
1073
1074 if not novld and not self.override_scheme and self.map.scheme then
1075 _uvl_validate_section(self, s)
1076 end
1077 end
1078 AbstractSection.parse_optionals(self, s)
1079
1080 if self.changed then
1081 self:push_events()
1082 end
1083 end
1084 end
1085
1086
1087 --[[
1088 TypedSection - A (set of) configuration section(s) defined by the type
1089 addremove: Defines whether the user can add/remove sections of this type
1090 anonymous: Allow creating anonymous sections
1091 validate: a validation function returning nil if the section is invalid
1092 ]]--
1093 TypedSection = class(AbstractSection)
1094
1095 function TypedSection.__init__(self, map, type, ...)
1096 AbstractSection.__init__(self, map, type, ...)
1097 Node._i18n(self, map.config, type, nil, ...)
1098
1099 self.template = "cbi/tsection"
1100 self.deps = {}
1101 self.anonymous = false
1102
1103 -- Use defaults from UVL
1104 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1105 local vs = self.map:get_scheme(self.sectiontype)
1106 self.addremove = not vs.unique and not vs.required
1107 self.dynamic = vs.dynamic
1108 self.anonymous = not vs.named
1109 self.title = self.title or vs.title
1110 self.description = self.description or vs.descr
1111 end
1112 end
1113
1114 -- Return all matching UCI sections for this TypedSection
1115 function TypedSection.cfgsections(self)
1116 local sections = {}
1117 self.map.uci:foreach(self.map.config, self.sectiontype,
1118 function (section)
1119 if self:checkscope(section[".name"]) then
1120 table.insert(sections, section[".name"])
1121 end
1122 end)
1123
1124 return sections
1125 end
1126
1127 -- Limits scope to sections that have certain option => value pairs
1128 function TypedSection.depends(self, option, value)
1129 table.insert(self.deps, {option=option, value=value})
1130 end
1131
1132 function TypedSection.parse(self, novld)
1133 if self.addremove then
1134 -- Remove
1135 local crval = REMOVE_PREFIX .. self.config
1136 local name = self.map:formvaluetable(crval)
1137 for k,v in pairs(name) do
1138 if k:sub(-2) == ".x" then
1139 k = k:sub(1, #k - 2)
1140 end
1141 if self:cfgvalue(k) and self:checkscope(k) then
1142 self:remove(k)
1143 end
1144 end
1145 end
1146
1147 local co
1148 for i, k in ipairs(self:cfgsections()) do
1149 AbstractSection.parse_dynamic(self, k)
1150 if self.map:submitstate() then
1151 Node.parse(self, k, novld)
1152
1153 if not novld and not self.override_scheme and self.map.scheme then
1154 _uvl_validate_section(self, k)
1155 end
1156 end
1157 AbstractSection.parse_optionals(self, k)
1158 end
1159
1160 if self.addremove then
1161 -- Create
1162 local created
1163 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1164 local name = self.map:formvalue(crval)
1165 if self.anonymous then
1166 if name then
1167 created = self:create()
1168 end
1169 else
1170 if name then
1171 -- Ignore if it already exists
1172 if self:cfgvalue(name) then
1173 name = nil;
1174 end
1175
1176 name = self:checkscope(name)
1177
1178 if not name then
1179 self.err_invalid = true
1180 end
1181
1182 if name and #name > 0 then
1183 created = self:create(name) and name
1184 if not created then
1185 self.invalid_cts = true
1186 end
1187 end
1188 end
1189 end
1190
1191 if created then
1192 AbstractSection.parse_optionals(self, created)
1193 end
1194 end
1195
1196 if created or self.changed then
1197 self:push_events()
1198 end
1199 end
1200
1201 -- Verifies scope of sections
1202 function TypedSection.checkscope(self, section)
1203 -- Check if we are not excluded
1204 if self.filter and not self:filter(section) then
1205 return nil
1206 end
1207
1208 -- Check if at least one dependency is met
1209 if #self.deps > 0 and self:cfgvalue(section) then
1210 local stat = false
1211
1212 for k, v in ipairs(self.deps) do
1213 if self:cfgvalue(section)[v.option] == v.value then
1214 stat = true
1215 end
1216 end
1217
1218 if not stat then
1219 return nil
1220 end
1221 end
1222
1223 return self:validate(section)
1224 end
1225
1226
1227 -- Dummy validate function
1228 function TypedSection.validate(self, section)
1229 return section
1230 end
1231
1232
1233 --[[
1234 AbstractValue - An abstract Value Type
1235 null: Value can be empty
1236 valid: A function returning the value if it is valid otherwise nil
1237 depends: A table of option => value pairs of which one must be true
1238 default: The default value
1239 size: The size of the input fields
1240 rmempty: Unset value if empty
1241 optional: This value is optional (see AbstractSection.optionals)
1242 ]]--
1243 AbstractValue = class(Node)
1244
1245 function AbstractValue.__init__(self, map, section, option, ...)
1246 Node.__init__(self, ...)
1247 self.section = section
1248 self.option = option
1249 self.map = map
1250 self.config = map.config
1251 self.tag_invalid = {}
1252 self.tag_missing = {}
1253 self.tag_reqerror = {}
1254 self.tag_error = {}
1255 self.deps = {}
1256 --self.cast = "string"
1257
1258 self.track_missing = false
1259 self.rmempty = true
1260 self.default = nil
1261 self.size = nil
1262 self.optional = false
1263 end
1264
1265 function AbstractValue.prepare(self)
1266 -- Use defaults from UVL
1267 if not self.override_scheme
1268 and self.map:get_scheme(self.section.sectiontype, self.option) then
1269 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1270 if self.cast == nil then
1271 self.cast = (vs.type == "list") and "list" or "string"
1272 end
1273 self.title = self.title or vs.title
1274 self.description = self.description or vs.descr
1275 if self.default == nil then
1276 self.default = vs.default
1277 end
1278
1279 if vs.depends and not self.override_dependencies then
1280 for i, deps in ipairs(vs.depends) do
1281 deps = _uvl_strip_remote_dependencies(deps)
1282 if next(deps) then
1283 self:depends(deps)
1284 end
1285 end
1286 end
1287 end
1288
1289 self.cast = self.cast or "string"
1290 end
1291
1292 -- Add a dependencie to another section field
1293 function AbstractValue.depends(self, field, value)
1294 local deps
1295 if type(field) == "string" then
1296 deps = {}
1297 deps[field] = value
1298 else
1299 deps = field
1300 end
1301
1302 table.insert(self.deps, {deps=deps, add=""})
1303 end
1304
1305 -- Generates the unique CBID
1306 function AbstractValue.cbid(self, section)
1307 return "cbid."..self.map.config.."."..section.."."..self.option
1308 end
1309
1310 -- Return whether this object should be created
1311 function AbstractValue.formcreated(self, section)
1312 local key = "cbi.opt."..self.config.."."..section
1313 return (self.map:formvalue(key) == self.option)
1314 end
1315
1316 -- Returns the formvalue for this object
1317 function AbstractValue.formvalue(self, section)
1318 return self.map:formvalue(self:cbid(section))
1319 end
1320
1321 function AbstractValue.additional(self, value)
1322 self.optional = value
1323 end
1324
1325 function AbstractValue.mandatory(self, value)
1326 self.rmempty = not value
1327 end
1328
1329 function AbstractValue.parse(self, section, novld)
1330 local fvalue = self:formvalue(section)
1331 local cvalue = self:cfgvalue(section)
1332
1333 -- If favlue and cvalue are both tables and have the same content
1334 -- make them identical
1335 if type(fvalue) == "table" and type(cvalue) == "table" then
1336 local equal = #fvalue == #cvalue
1337 if equal then
1338 for i=1, #fvalue do
1339 if cvalue[i] ~= fvalue[i] then
1340 equal = false
1341 end
1342 end
1343 end
1344 if equal then
1345 fvalue = cvalue
1346 end
1347 end
1348
1349 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1350 fvalue = self:transform(self:validate(fvalue, section))
1351 if not fvalue and not novld then
1352 if self.error then
1353 self.error[section] = "invalid"
1354 else
1355 self.error = { [section] = "invalid" }
1356 end
1357 if self.section.error then
1358 table.insert(self.section.error[section], "invalid")
1359 else
1360 self.section.error = {[section] = {"invalid"}}
1361 end
1362 self.map.save = false
1363 end
1364 if fvalue and not (fvalue == cvalue) then
1365 if self:write(section, fvalue) then
1366 -- Push events
1367 self.section.changed = true
1368 --luci.util.append(self.map.events, self.events)
1369 end
1370 end
1371 else -- Unset the UCI or error
1372 if self.rmempty or self.optional then
1373 if self:remove(section) then
1374 -- Push events
1375 self.section.changed = true
1376 --luci.util.append(self.map.events, self.events)
1377 end
1378 elseif cvalue ~= fvalue and not novld then
1379 self:write(section, fvalue or "")
1380 if self.error then
1381 self.error[section] = "missing"
1382 else
1383 self.error = { [section] = "missing" }
1384 end
1385 self.map.save = false
1386 end
1387 end
1388 end
1389
1390 -- Render if this value exists or if it is mandatory
1391 function AbstractValue.render(self, s, scope)
1392 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1393 scope = scope or {}
1394 scope.section = s
1395 scope.cbid = self:cbid(s)
1396 scope.striptags = luci.util.striptags
1397 scope.pcdata = luci.util.pcdata
1398
1399 scope.ifattr = function(cond,key,val)
1400 if cond then
1401 return string.format(
1402 ' %s="%s"', tostring(key),
1403 luci.util.pcdata(tostring( val
1404 or scope[key]
1405 or (type(self[key]) ~= "function" and self[key])
1406 or "" ))
1407 )
1408 else
1409 return ''
1410 end
1411 end
1412
1413 scope.attr = function(...)
1414 return scope.ifattr( true, ... )
1415 end
1416
1417 Node.render(self, scope)
1418 end
1419 end
1420
1421 -- Return the UCI value of this object
1422 function AbstractValue.cfgvalue(self, section)
1423 local value = self.map:get(section, self.option)
1424 if not value then
1425 return nil
1426 elseif not self.cast or self.cast == type(value) then
1427 return value
1428 elseif self.cast == "string" then
1429 if type(value) == "table" then
1430 return value[1]
1431 end
1432 elseif self.cast == "table" then
1433 return luci.util.split(value, "%s+", nil, true)
1434 end
1435 end
1436
1437 -- Validate the form value
1438 function AbstractValue.validate(self, value)
1439 return value
1440 end
1441
1442 AbstractValue.transform = AbstractValue.validate
1443
1444
1445 -- Write to UCI
1446 function AbstractValue.write(self, section, value)
1447 return self.map:set(section, self.option, value)
1448 end
1449
1450 -- Remove from UCI
1451 function AbstractValue.remove(self, section)
1452 return self.map:del(section, self.option)
1453 end
1454
1455
1456
1457
1458 --[[
1459 Value - A one-line value
1460 maxlength: The maximum length
1461 ]]--
1462 Value = class(AbstractValue)
1463
1464 function Value.__init__(self, ...)
1465 AbstractValue.__init__(self, ...)
1466 self.template = "cbi/value"
1467 self.keylist = {}
1468 self.vallist = {}
1469 end
1470
1471 function Value.value(self, key, val)
1472 val = val or key
1473 table.insert(self.keylist, tostring(key))
1474 table.insert(self.vallist, tostring(val))
1475 end
1476
1477
1478 -- DummyValue - This does nothing except being there
1479 DummyValue = class(AbstractValue)
1480
1481 function DummyValue.__init__(self, ...)
1482 AbstractValue.__init__(self, ...)
1483 self.template = "cbi/dvalue"
1484 self.value = nil
1485 end
1486
1487 function DummyValue.cfgvalue(self, section)
1488 local value
1489 if self.value then
1490 if type(self.value) == "function" then
1491 value = self:value(section)
1492 else
1493 value = self.value
1494 end
1495 else
1496 value = AbstractValue.cfgvalue(self, section)
1497 end
1498 return value
1499 end
1500
1501 function DummyValue.parse(self)
1502
1503 end
1504
1505
1506 --[[
1507 Flag - A flag being enabled or disabled
1508 ]]--
1509 Flag = class(AbstractValue)
1510
1511 function Flag.__init__(self, ...)
1512 AbstractValue.__init__(self, ...)
1513 self.template = "cbi/fvalue"
1514
1515 self.enabled = "1"
1516 self.disabled = "0"
1517 end
1518
1519 -- A flag can only have two states: set or unset
1520 function Flag.parse(self, section)
1521 local fvalue = self:formvalue(section)
1522
1523 if fvalue then
1524 fvalue = self.enabled
1525 else
1526 fvalue = self.disabled
1527 end
1528
1529 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1530 if not(fvalue == self:cfgvalue(section)) then
1531 self:write(section, fvalue)
1532 end
1533 else
1534 self:remove(section)
1535 end
1536 end
1537
1538
1539
1540 --[[
1541 ListValue - A one-line value predefined in a list
1542 widget: The widget that will be used (select, radio)
1543 ]]--
1544 ListValue = class(AbstractValue)
1545
1546 function ListValue.__init__(self, ...)
1547 AbstractValue.__init__(self, ...)
1548 self.template = "cbi/lvalue"
1549
1550 self.keylist = {}
1551 self.vallist = {}
1552 self.size = 1
1553 self.widget = "select"
1554 end
1555
1556 function ListValue.prepare(self, ...)
1557 AbstractValue.prepare(self, ...)
1558 if not self.override_scheme
1559 and self.map:get_scheme(self.section.sectiontype, self.option) then
1560 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1561 if self.value and vs.valuelist and not self.override_values then
1562 for k, v in ipairs(vs.valuelist) do
1563 local deps = {}
1564 if not self.override_dependencies
1565 and vs.enum_depends and vs.enum_depends[v.value] then
1566 for i, dep in ipairs(vs.enum_depends[v.value]) do
1567 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1568 end
1569 end
1570 self:value(v.value, v.title or v.value, unpack(deps))
1571 end
1572 end
1573 end
1574 end
1575
1576 function ListValue.value(self, key, val, ...)
1577 if luci.util.contains(self.keylist, key) then
1578 return
1579 end
1580
1581 val = val or key
1582 table.insert(self.keylist, tostring(key))
1583 table.insert(self.vallist, tostring(val))
1584
1585 for i, deps in ipairs({...}) do
1586 table.insert(self.deps, {add = "-"..key, deps=deps})
1587 end
1588 end
1589
1590 function ListValue.validate(self, val)
1591 if luci.util.contains(self.keylist, val) then
1592 return val
1593 else
1594 return nil
1595 end
1596 end
1597
1598
1599
1600 --[[
1601 MultiValue - Multiple delimited values
1602 widget: The widget that will be used (select, checkbox)
1603 delimiter: The delimiter that will separate the values (default: " ")
1604 ]]--
1605 MultiValue = class(AbstractValue)
1606
1607 function MultiValue.__init__(self, ...)
1608 AbstractValue.__init__(self, ...)
1609 self.template = "cbi/mvalue"
1610
1611 self.keylist = {}
1612 self.vallist = {}
1613
1614 self.widget = "checkbox"
1615 self.delimiter = " "
1616 end
1617
1618 function MultiValue.render(self, ...)
1619 if self.widget == "select" and not self.size then
1620 self.size = #self.vallist
1621 end
1622
1623 AbstractValue.render(self, ...)
1624 end
1625
1626 function MultiValue.value(self, key, val)
1627 if luci.util.contains(self.keylist, key) then
1628 return
1629 end
1630
1631 val = val or key
1632 table.insert(self.keylist, tostring(key))
1633 table.insert(self.vallist, tostring(val))
1634 end
1635
1636 function MultiValue.valuelist(self, section)
1637 local val = self:cfgvalue(section)
1638
1639 if not(type(val) == "string") then
1640 return {}
1641 end
1642
1643 return luci.util.split(val, self.delimiter)
1644 end
1645
1646 function MultiValue.validate(self, val)
1647 val = (type(val) == "table") and val or {val}
1648
1649 local result
1650
1651 for i, value in ipairs(val) do
1652 if luci.util.contains(self.keylist, value) then
1653 result = result and (result .. self.delimiter .. value) or value
1654 end
1655 end
1656
1657 return result
1658 end
1659
1660
1661 StaticList = class(MultiValue)
1662
1663 function StaticList.__init__(self, ...)
1664 MultiValue.__init__(self, ...)
1665 self.cast = "table"
1666 self.valuelist = self.cfgvalue
1667
1668 if not self.override_scheme
1669 and self.map:get_scheme(self.section.sectiontype, self.option) then
1670 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1671 if self.value and vs.values and not self.override_values then
1672 for k, v in pairs(vs.values) do
1673 self:value(k, v)
1674 end
1675 end
1676 end
1677 end
1678
1679 function StaticList.validate(self, value)
1680 value = (type(value) == "table") and value or {value}
1681
1682 local valid = {}
1683 for i, v in ipairs(value) do
1684 if luci.util.contains(self.keylist, v) then
1685 table.insert(valid, v)
1686 end
1687 end
1688 return valid
1689 end
1690
1691
1692 DynamicList = class(AbstractValue)
1693
1694 function DynamicList.__init__(self, ...)
1695 AbstractValue.__init__(self, ...)
1696 self.template = "cbi/dynlist"
1697 self.cast = "table"
1698 self.keylist = {}
1699 self.vallist = {}
1700 end
1701
1702 function DynamicList.value(self, key, val)
1703 val = val or key
1704 table.insert(self.keylist, tostring(key))
1705 table.insert(self.vallist, tostring(val))
1706 end
1707
1708 function DynamicList.write(self, ...)
1709 self.map.proceed = true
1710 return AbstractValue.write(self, ...)
1711 end
1712
1713 function DynamicList.formvalue(self, section)
1714 local value = AbstractValue.formvalue(self, section)
1715 value = (type(value) == "table") and value or {value}
1716
1717 local valid = {}
1718 for i, v in ipairs(value) do
1719 if v and #v > 0
1720 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1721 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1722 table.insert(valid, v)
1723 end
1724 end
1725
1726 return valid
1727 end
1728
1729
1730 --[[
1731 TextValue - A multi-line value
1732 rows: Rows
1733 ]]--
1734 TextValue = class(AbstractValue)
1735
1736 function TextValue.__init__(self, ...)
1737 AbstractValue.__init__(self, ...)
1738 self.template = "cbi/tvalue"
1739 end
1740
1741 --[[
1742 Button
1743 ]]--
1744 Button = class(AbstractValue)
1745
1746 function Button.__init__(self, ...)
1747 AbstractValue.__init__(self, ...)
1748 self.template = "cbi/button"
1749 self.inputstyle = nil
1750 self.rmempty = true
1751 end
1752
1753
1754 FileUpload = class(AbstractValue)
1755
1756 function FileUpload.__init__(self, ...)
1757 AbstractValue.__init__(self, ...)
1758 self.template = "cbi/upload"
1759 if not self.map.upload_fields then
1760 self.map.upload_fields = { self }
1761 else
1762 self.map.upload_fields[#self.map.upload_fields+1] = self
1763 end
1764 end
1765
1766 function FileUpload.formcreated(self, section)
1767 return AbstractValue.formcreated(self, section) or
1768 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1769 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1770 end
1771
1772 function FileUpload.cfgvalue(self, section)
1773 local val = AbstractValue.cfgvalue(self, section)
1774 if val and fs.access(val) then
1775 return val
1776 end
1777 return nil
1778 end
1779
1780 function FileUpload.formvalue(self, section)
1781 local val = AbstractValue.formvalue(self, section)
1782 if val then
1783 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1784 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1785 then
1786 return val
1787 end
1788 fs.unlink(val)
1789 self.value = nil
1790 end
1791 return nil
1792 end
1793
1794 function FileUpload.remove(self, section)
1795 local val = AbstractValue.formvalue(self, section)
1796 if val and fs.access(val) then fs.unlink(val) end
1797 return AbstractValue.remove(self, section)
1798 end
1799
1800
1801 FileBrowser = class(AbstractValue)
1802
1803 function FileBrowser.__init__(self, ...)
1804 AbstractValue.__init__(self, ...)
1805 self.template = "cbi/browser"
1806 end