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