libs/web: fix dynamic list handling
[project/luci.git] / libs / web / 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 self:_run_hooks("on_save", "on_before_save")
308 for i, config in ipairs(self.parsechain) do
309 self.uci:save(config)
310 end
311 self:_run_hooks("on_after_save")
312 if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
313 self:_run_hooks("on_before_commit")
314 for i, config in ipairs(self.parsechain) do
315 self.uci:commit(config)
316
317 -- Refresh data because commit changes section names
318 self.uci:load(config)
319 end
320 self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
321 if self.apply_on_parse then
322 self.uci:apply(self.parsechain)
323 self:_run_hooks("on_apply", "on_after_apply")
324 else
325 -- This is evaluated by the dispatcher and delegated to the
326 -- template which in turn fires XHR to perform the actual
327 -- apply actions.
328 self.apply_needed = true
329 end
330
331 -- Reparse sections
332 Node.parse(self, true)
333
334 end
335 for i, config in ipairs(self.parsechain) do
336 self.uci:unload(config)
337 end
338 if type(self.commit_handler) == "function" then
339 self:commit_handler(self:submitstate())
340 end
341 end
342
343 if self:submitstate() then
344 if not self.save then
345 self.state = FORM_INVALID
346 elseif self.proceed then
347 self.state = FORM_PROCEED
348 else
349 self.state = self.changed and FORM_CHANGED or FORM_VALID
350 end
351 else
352 self.state = FORM_NODATA
353 end
354
355 return self:state_handler(self.state)
356 end
357
358 function Map.render(self, ...)
359 self:_run_hooks("on_init")
360 Node.render(self, ...)
361 end
362
363 -- Creates a child section
364 function Map.section(self, class, ...)
365 if instanceof(class, AbstractSection) then
366 local obj = class(self, ...)
367 self:append(obj)
368 return obj
369 else
370 error("class must be a descendent of AbstractSection")
371 end
372 end
373
374 -- UCI add
375 function Map.add(self, sectiontype)
376 return self.uci:add(self.config, sectiontype)
377 end
378
379 -- UCI set
380 function Map.set(self, section, option, value)
381 if option then
382 return self.uci:set(self.config, section, option, value)
383 else
384 return self.uci:set(self.config, section, value)
385 end
386 end
387
388 -- UCI del
389 function Map.del(self, section, option)
390 if option then
391 return self.uci:delete(self.config, section, option)
392 else
393 return self.uci:delete(self.config, section)
394 end
395 end
396
397 -- UCI get
398 function Map.get(self, section, option)
399 if not section then
400 return self.uci:get_all(self.config)
401 elseif option then
402 return self.uci:get(self.config, section, option)
403 else
404 return self.uci:get_all(self.config, section)
405 end
406 end
407
408 --[[
409 Compound - Container
410 ]]--
411 Compound = class(Node)
412
413 function Compound.__init__(self, ...)
414 Node.__init__(self)
415 self.template = "cbi/compound"
416 self.children = {...}
417 end
418
419 function Compound.populate_delegator(self, delegator)
420 for _, v in ipairs(self.children) do
421 v.delegator = delegator
422 end
423 end
424
425 function Compound.parse(self, ...)
426 local cstate, state = 0
427
428 for k, child in ipairs(self.children) do
429 cstate = child:parse(...)
430 state = (not state or cstate < state) and cstate or state
431 end
432
433 return state
434 end
435
436
437 --[[
438 Delegator - Node controller
439 ]]--
440 Delegator = class(Node)
441 function Delegator.__init__(self, ...)
442 Node.__init__(self, ...)
443 self.nodes = {}
444 self.defaultpath = {}
445 self.pageaction = false
446 self.readinput = true
447 self.allow_reset = false
448 self.allow_cancel = false
449 self.allow_back = false
450 self.allow_finish = false
451 self.template = "cbi/delegator"
452 end
453
454 function Delegator.set(self, name, node)
455 assert(not self.nodes[name], "Duplicate entry")
456
457 self.nodes[name] = node
458 end
459
460 function Delegator.add(self, name, node)
461 node = self:set(name, node)
462 self.defaultpath[#self.defaultpath+1] = name
463 end
464
465 function Delegator.insert_after(self, name, after)
466 local n = #self.chain + 1
467 for k, v in ipairs(self.chain) do
468 if v == after then
469 n = k + 1
470 break
471 end
472 end
473 table.insert(self.chain, n, name)
474 end
475
476 function Delegator.set_route(self, ...)
477 local n, chain, route = 0, self.chain, {...}
478 for i = 1, #chain do
479 if chain[i] == self.current then
480 n = i
481 break
482 end
483 end
484 for i = 1, #route do
485 n = n + 1
486 chain[n] = route[i]
487 end
488 for i = n + 1, #chain do
489 chain[i] = nil
490 end
491 end
492
493 function Delegator.get(self, name)
494 local node = self.nodes[name]
495
496 if type(node) == "string" then
497 node = load(node, name)
498 end
499
500 if type(node) == "table" and getmetatable(node) == nil then
501 node = Compound(unpack(node))
502 end
503
504 return node
505 end
506
507 function Delegator.parse(self, ...)
508 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
509 if self:_run_hooks("on_cancel") then
510 return FORM_DONE
511 end
512 end
513
514 if not Map.formvalue(self, "cbi.delg.current") then
515 self:_run_hooks("on_init")
516 end
517
518 local newcurrent
519 self.chain = self.chain or self:get_chain()
520 self.current = self.current or self:get_active()
521 self.active = self.active or self:get(self.current)
522 assert(self.active, "Invalid state")
523
524 local stat = FORM_DONE
525 if type(self.active) ~= "function" then
526 self.active:populate_delegator(self)
527 stat = self.active:parse()
528 else
529 self:active()
530 end
531
532 if stat > FORM_PROCEED then
533 if Map.formvalue(self, "cbi.delg.back") then
534 newcurrent = self:get_prev(self.current)
535 else
536 newcurrent = self:get_next(self.current)
537 end
538 elseif stat < FORM_PROCEED then
539 return stat
540 end
541
542
543 if not Map.formvalue(self, "cbi.submit") then
544 return FORM_NODATA
545 elseif stat > FORM_PROCEED
546 and (not newcurrent or not self:get(newcurrent)) then
547 return self:_run_hook("on_done") or FORM_DONE
548 else
549 self.current = newcurrent or self.current
550 self.active = self:get(self.current)
551 if type(self.active) ~= "function" then
552 self.active:populate_delegator(self)
553 local stat = self.active:parse(false)
554 if stat == FORM_SKIP then
555 return self:parse(...)
556 else
557 return FORM_PROCEED
558 end
559 else
560 return self:parse(...)
561 end
562 end
563 end
564
565 function Delegator.get_next(self, state)
566 for k, v in ipairs(self.chain) do
567 if v == state then
568 return self.chain[k+1]
569 end
570 end
571 end
572
573 function Delegator.get_prev(self, state)
574 for k, v in ipairs(self.chain) do
575 if v == state then
576 return self.chain[k-1]
577 end
578 end
579 end
580
581 function Delegator.get_chain(self)
582 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
583 return type(x) == "table" and x or {x}
584 end
585
586 function Delegator.get_active(self)
587 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
588 end
589
590 --[[
591 Page - A simple node
592 ]]--
593
594 Page = class(Node)
595 Page.__init__ = Node.__init__
596 Page.parse = function() end
597
598
599 --[[
600 SimpleForm - A Simple non-UCI form
601 ]]--
602 SimpleForm = class(Node)
603
604 function SimpleForm.__init__(self, config, title, description, data)
605 Node.__init__(self, title, description)
606 self.config = config
607 self.data = data or {}
608 self.template = "cbi/simpleform"
609 self.dorender = true
610 self.pageaction = false
611 self.readinput = true
612 end
613
614 SimpleForm.formvalue = Map.formvalue
615 SimpleForm.formvaluetable = Map.formvaluetable
616
617 function SimpleForm.parse(self, readinput, ...)
618 self.readinput = (readinput ~= false)
619
620 if self:formvalue("cbi.skip") then
621 return FORM_SKIP
622 end
623
624 if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
625 return FORM_DONE
626 end
627
628 if self:submitstate() then
629 Node.parse(self, 1, ...)
630 end
631
632 local valid = true
633 for k, j in ipairs(self.children) do
634 for i, v in ipairs(j.children) do
635 valid = valid
636 and (not v.tag_missing or not v.tag_missing[1])
637 and (not v.tag_invalid or not v.tag_invalid[1])
638 and (not v.error)
639 end
640 end
641
642 local state =
643 not self:submitstate() and FORM_NODATA
644 or valid and FORM_VALID
645 or FORM_INVALID
646
647 self.dorender = not self.handle
648 if self.handle then
649 local nrender, nstate = self:handle(state, self.data)
650 self.dorender = self.dorender or (nrender ~= false)
651 state = nstate or state
652 end
653 return state
654 end
655
656 function SimpleForm.render(self, ...)
657 if self.dorender then
658 Node.render(self, ...)
659 end
660 end
661
662 function SimpleForm.submitstate(self)
663 return self:formvalue("cbi.submit")
664 end
665
666 function SimpleForm.section(self, class, ...)
667 if instanceof(class, AbstractSection) then
668 local obj = class(self, ...)
669 self:append(obj)
670 return obj
671 else
672 error("class must be a descendent of AbstractSection")
673 end
674 end
675
676 -- Creates a child field
677 function SimpleForm.field(self, class, ...)
678 local section
679 for k, v in ipairs(self.children) do
680 if instanceof(v, SimpleSection) then
681 section = v
682 break
683 end
684 end
685 if not section then
686 section = self:section(SimpleSection)
687 end
688
689 if instanceof(class, AbstractValue) then
690 local obj = class(self, section, ...)
691 obj.track_missing = true
692 section:append(obj)
693 return obj
694 else
695 error("class must be a descendent of AbstractValue")
696 end
697 end
698
699 function SimpleForm.set(self, section, option, value)
700 self.data[option] = value
701 end
702
703
704 function SimpleForm.del(self, section, option)
705 self.data[option] = nil
706 end
707
708
709 function SimpleForm.get(self, section, option)
710 return self.data[option]
711 end
712
713
714 function SimpleForm.get_scheme()
715 return nil
716 end
717
718
719 Form = class(SimpleForm)
720
721 function Form.__init__(self, ...)
722 SimpleForm.__init__(self, ...)
723 self.embedded = true
724 end
725
726
727 --[[
728 AbstractSection
729 ]]--
730 AbstractSection = class(Node)
731
732 function AbstractSection.__init__(self, map, sectiontype, ...)
733 Node.__init__(self, ...)
734 self.sectiontype = sectiontype
735 self.map = map
736 self.config = map.config
737 self.optionals = {}
738 self.defaults = {}
739 self.fields = {}
740 self.tag_error = {}
741 self.tag_invalid = {}
742 self.tag_deperror = {}
743 self.changed = false
744
745 self.optional = true
746 self.addremove = false
747 self.dynamic = false
748 end
749
750 -- Define a tab for the section
751 function AbstractSection.tab(self, tab, title, desc)
752 self.tabs = self.tabs or { }
753 self.tab_names = self.tab_names or { }
754
755 self.tab_names[#self.tab_names+1] = tab
756 self.tabs[tab] = {
757 title = title,
758 description = desc,
759 childs = { }
760 }
761 end
762
763 -- Check whether the section has tabs
764 function AbstractSection.has_tabs(self)
765 return (self.tabs ~= nil) and (next(self.tabs) ~= nil)
766 end
767
768 -- Appends a new option
769 function AbstractSection.option(self, class, option, ...)
770 if instanceof(class, AbstractValue) then
771 local obj = class(self.map, self, option, ...)
772 self:append(obj)
773 self.fields[option] = obj
774 return obj
775 elseif class == true then
776 error("No valid class was given and autodetection failed.")
777 else
778 error("class must be a descendant of AbstractValue")
779 end
780 end
781
782 -- Appends a new tabbed option
783 function AbstractSection.taboption(self, tab, ...)
784
785 assert(tab and self.tabs and self.tabs[tab],
786 "Cannot assign option to not existing tab %q" % tostring(tab))
787
788 local l = self.tabs[tab].childs
789 local o = AbstractSection.option(self, ...)
790
791 if o then l[#l+1] = o end
792
793 return o
794 end
795
796 -- Render a single tab
797 function AbstractSection.render_tab(self, tab, ...)
798
799 assert(tab and self.tabs and self.tabs[tab],
800 "Cannot render not existing tab %q" % tostring(tab))
801
802 for _, node in ipairs(self.tabs[tab].childs) do
803 node:render(...)
804 end
805 end
806
807 -- Parse optional options
808 function AbstractSection.parse_optionals(self, section)
809 if not self.optional then
810 return
811 end
812
813 self.optionals[section] = {}
814
815 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
816 for k,v in ipairs(self.children) do
817 if v.optional and not v:cfgvalue(section) and not self:has_tabs() then
818 if field == v.option then
819 field = nil
820 self.map.proceed = true
821 else
822 table.insert(self.optionals[section], v)
823 end
824 end
825 end
826
827 if field and #field > 0 and self.dynamic then
828 self:add_dynamic(field)
829 end
830 end
831
832 -- Add a dynamic option
833 function AbstractSection.add_dynamic(self, field, optional)
834 local o = self:option(Value, field, field)
835 o.optional = optional
836 end
837
838 -- Parse all dynamic options
839 function AbstractSection.parse_dynamic(self, section)
840 if not self.dynamic then
841 return
842 end
843
844 local arr = luci.util.clone(self:cfgvalue(section))
845 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
846 for k, v in pairs(form) do
847 arr[k] = v
848 end
849
850 for key,val in pairs(arr) do
851 local create = true
852
853 for i,c in ipairs(self.children) do
854 if c.option == key then
855 create = false
856 end
857 end
858
859 if create and key:sub(1, 1) ~= "." then
860 self.map.proceed = true
861 self:add_dynamic(key, true)
862 end
863 end
864 end
865
866 -- Returns the section's UCI table
867 function AbstractSection.cfgvalue(self, section)
868 return self.map:get(section)
869 end
870
871 -- Push events
872 function AbstractSection.push_events(self)
873 --luci.util.append(self.map.events, self.events)
874 self.map.changed = true
875 end
876
877 -- Removes the section
878 function AbstractSection.remove(self, section)
879 self.map.proceed = true
880 return self.map:del(section)
881 end
882
883 -- Creates the section
884 function AbstractSection.create(self, section)
885 local stat
886
887 if section then
888 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
889 else
890 section = self.map:add(self.sectiontype)
891 stat = section
892 end
893
894 if stat then
895 for k,v in pairs(self.children) do
896 if v.default then
897 self.map:set(section, v.option, v.default)
898 end
899 end
900
901 for k,v in pairs(self.defaults) do
902 self.map:set(section, k, v)
903 end
904 end
905
906 self.map.proceed = true
907
908 return stat
909 end
910
911
912 SimpleSection = class(AbstractSection)
913
914 function SimpleSection.__init__(self, form, ...)
915 AbstractSection.__init__(self, form, nil, ...)
916 self.template = "cbi/nullsection"
917 end
918
919
920 Table = class(AbstractSection)
921
922 function Table.__init__(self, form, data, ...)
923 local datasource = {}
924 local tself = self
925 datasource.config = "table"
926 self.data = data or {}
927
928 datasource.formvalue = Map.formvalue
929 datasource.formvaluetable = Map.formvaluetable
930 datasource.readinput = true
931
932 function datasource.get(self, section, option)
933 return tself.data[section] and tself.data[section][option]
934 end
935
936 function datasource.submitstate(self)
937 return Map.formvalue(self, "cbi.submit")
938 end
939
940 function datasource.del(...)
941 return true
942 end
943
944 function datasource.get_scheme()
945 return nil
946 end
947
948 AbstractSection.__init__(self, datasource, "table", ...)
949 self.template = "cbi/tblsection"
950 self.rowcolors = true
951 self.anonymous = true
952 end
953
954 function Table.parse(self, readinput)
955 self.map.readinput = (readinput ~= false)
956 for i, k in ipairs(self:cfgsections()) do
957 if self.map:submitstate() then
958 Node.parse(self, k)
959 end
960 end
961 end
962
963 function Table.cfgsections(self)
964 local sections = {}
965
966 for i, v in luci.util.kspairs(self.data) do
967 table.insert(sections, i)
968 end
969
970 return sections
971 end
972
973 function Table.update(self, data)
974 self.data = data
975 end
976
977
978
979 --[[
980 NamedSection - A fixed configuration section defined by its name
981 ]]--
982 NamedSection = class(AbstractSection)
983
984 function NamedSection.__init__(self, map, section, stype, ...)
985 AbstractSection.__init__(self, map, stype, ...)
986
987 -- Defaults
988 self.addremove = false
989 self.template = "cbi/nsection"
990 self.section = section
991 end
992
993 function NamedSection.parse(self, novld)
994 local s = self.section
995 local active = self:cfgvalue(s)
996
997 if self.addremove then
998 local path = self.config.."."..s
999 if active then -- Remove the section
1000 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1001 self:push_events()
1002 return
1003 end
1004 else -- Create and apply default values
1005 if self.map:formvalue("cbi.cns."..path) then
1006 self:create(s)
1007 return
1008 end
1009 end
1010 end
1011
1012 if active then
1013 AbstractSection.parse_dynamic(self, s)
1014 if self.map:submitstate() then
1015 Node.parse(self, s)
1016 end
1017 AbstractSection.parse_optionals(self, s)
1018
1019 if self.changed then
1020 self:push_events()
1021 end
1022 end
1023 end
1024
1025
1026 --[[
1027 TypedSection - A (set of) configuration section(s) defined by the type
1028 addremove: Defines whether the user can add/remove sections of this type
1029 anonymous: Allow creating anonymous sections
1030 validate: a validation function returning nil if the section is invalid
1031 ]]--
1032 TypedSection = class(AbstractSection)
1033
1034 function TypedSection.__init__(self, map, type, ...)
1035 AbstractSection.__init__(self, map, type, ...)
1036
1037 self.template = "cbi/tsection"
1038 self.deps = {}
1039 self.anonymous = false
1040 end
1041
1042 -- Return all matching UCI sections for this TypedSection
1043 function TypedSection.cfgsections(self)
1044 local sections = {}
1045 self.map.uci:foreach(self.map.config, self.sectiontype,
1046 function (section)
1047 if self:checkscope(section[".name"]) then
1048 table.insert(sections, section[".name"])
1049 end
1050 end)
1051
1052 return sections
1053 end
1054
1055 -- Limits scope to sections that have certain option => value pairs
1056 function TypedSection.depends(self, option, value)
1057 table.insert(self.deps, {option=option, value=value})
1058 end
1059
1060 function TypedSection.parse(self, novld)
1061 if self.addremove then
1062 -- Remove
1063 local crval = REMOVE_PREFIX .. self.config
1064 local name = self.map:formvaluetable(crval)
1065 for k,v in pairs(name) do
1066 if k:sub(-2) == ".x" then
1067 k = k:sub(1, #k - 2)
1068 end
1069 if self:cfgvalue(k) and self:checkscope(k) then
1070 self:remove(k)
1071 end
1072 end
1073 end
1074
1075 local co
1076 for i, k in ipairs(self:cfgsections()) do
1077 AbstractSection.parse_dynamic(self, k)
1078 if self.map:submitstate() then
1079 Node.parse(self, k, novld)
1080 end
1081 AbstractSection.parse_optionals(self, k)
1082 end
1083
1084 if self.addremove then
1085 -- Create
1086 local created
1087 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1088 local name = self.map:formvalue(crval)
1089 if self.anonymous then
1090 if name then
1091 created = self:create()
1092 end
1093 else
1094 if name then
1095 -- Ignore if it already exists
1096 if self:cfgvalue(name) then
1097 name = nil;
1098 end
1099
1100 name = self:checkscope(name)
1101
1102 if not name then
1103 self.err_invalid = true
1104 end
1105
1106 if name and #name > 0 then
1107 created = self:create(name) and name
1108 if not created then
1109 self.invalid_cts = true
1110 end
1111 end
1112 end
1113 end
1114
1115 if created then
1116 AbstractSection.parse_optionals(self, created)
1117 end
1118 end
1119
1120 if created or self.changed then
1121 self:push_events()
1122 end
1123 end
1124
1125 -- Verifies scope of sections
1126 function TypedSection.checkscope(self, section)
1127 -- Check if we are not excluded
1128 if self.filter and not self:filter(section) then
1129 return nil
1130 end
1131
1132 -- Check if at least one dependency is met
1133 if #self.deps > 0 and self:cfgvalue(section) then
1134 local stat = false
1135
1136 for k, v in ipairs(self.deps) do
1137 if self:cfgvalue(section)[v.option] == v.value then
1138 stat = true
1139 end
1140 end
1141
1142 if not stat then
1143 return nil
1144 end
1145 end
1146
1147 return self:validate(section)
1148 end
1149
1150
1151 -- Dummy validate function
1152 function TypedSection.validate(self, section)
1153 return section
1154 end
1155
1156
1157 --[[
1158 AbstractValue - An abstract Value Type
1159 null: Value can be empty
1160 valid: A function returning the value if it is valid otherwise nil
1161 depends: A table of option => value pairs of which one must be true
1162 default: The default value
1163 size: The size of the input fields
1164 rmempty: Unset value if empty
1165 optional: This value is optional (see AbstractSection.optionals)
1166 ]]--
1167 AbstractValue = class(Node)
1168
1169 function AbstractValue.__init__(self, map, section, option, ...)
1170 Node.__init__(self, ...)
1171 self.section = section
1172 self.option = option
1173 self.map = map
1174 self.config = map.config
1175 self.tag_invalid = {}
1176 self.tag_missing = {}
1177 self.tag_reqerror = {}
1178 self.tag_error = {}
1179 self.deps = {}
1180 self.subdeps = {}
1181 --self.cast = "string"
1182
1183 self.track_missing = false
1184 self.rmempty = true
1185 self.default = nil
1186 self.size = nil
1187 self.optional = false
1188 end
1189
1190 function AbstractValue.prepare(self)
1191 self.cast = self.cast or "string"
1192 end
1193
1194 -- Add a dependencie to another section field
1195 function AbstractValue.depends(self, field, value)
1196 local deps
1197 if type(field) == "string" then
1198 deps = {}
1199 deps[field] = value
1200 else
1201 deps = field
1202 end
1203
1204 table.insert(self.deps, {deps=deps, add=""})
1205 end
1206
1207 -- Generates the unique CBID
1208 function AbstractValue.cbid(self, section)
1209 return "cbid."..self.map.config.."."..section.."."..self.option
1210 end
1211
1212 -- Return whether this object should be created
1213 function AbstractValue.formcreated(self, section)
1214 local key = "cbi.opt."..self.config.."."..section
1215 return (self.map:formvalue(key) == self.option)
1216 end
1217
1218 -- Returns the formvalue for this object
1219 function AbstractValue.formvalue(self, section)
1220 return self.map:formvalue(self:cbid(section))
1221 end
1222
1223 function AbstractValue.additional(self, value)
1224 self.optional = value
1225 end
1226
1227 function AbstractValue.mandatory(self, value)
1228 self.rmempty = not value
1229 end
1230
1231 function AbstractValue.add_error(self, section, type, msg)
1232 self.error = self.error or { }
1233 self.error[section] = msg or type
1234
1235 self.section.error = self.section.error or { }
1236 self.section.error[section] = self.section.error[section] or { }
1237 table.insert(self.section.error[section], msg or type)
1238
1239 if type == "invalid" then
1240 self.tag_invalid[section] = true
1241 elseif type == "missing" then
1242 self.tag_missing[section] = true
1243 end
1244
1245 self.tag_error[section] = true
1246 self.map.save = false
1247 end
1248
1249 function AbstractValue.parse(self, section, novld)
1250 local fvalue = self:formvalue(section)
1251 local cvalue = self:cfgvalue(section)
1252
1253 -- If favlue and cvalue are both tables and have the same content
1254 -- make them identical
1255 if type(fvalue) == "table" and type(cvalue) == "table" then
1256 local equal = #fvalue == #cvalue
1257 if equal then
1258 for i=1, #fvalue do
1259 if cvalue[i] ~= fvalue[i] then
1260 equal = false
1261 end
1262 end
1263 end
1264 if equal then
1265 fvalue = cvalue
1266 end
1267 end
1268
1269 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1270 local val_err
1271 fvalue, val_err = self:validate(fvalue, section)
1272 fvalue = self:transform(fvalue)
1273
1274 if not fvalue and not novld then
1275 self:add_error(section, "invalid", val_err)
1276 end
1277
1278 if fvalue and (self.forcewrite or not (fvalue == cvalue)) then
1279 if self:write(section, fvalue) then
1280 -- Push events
1281 self.section.changed = true
1282 --luci.util.append(self.map.events, self.events)
1283 end
1284 end
1285 else -- Unset the UCI or error
1286 if self.rmempty or self.optional then
1287 if self:remove(section) then
1288 -- Push events
1289 self.section.changed = true
1290 --luci.util.append(self.map.events, self.events)
1291 end
1292 elseif cvalue ~= fvalue and not novld then
1293 -- trigger validator with nil value to get custom user error msg.
1294 local _, val_err = self:validate(nil, section)
1295 self:add_error(section, "missing", val_err)
1296 end
1297 end
1298 end
1299
1300 -- Render if this value exists or if it is mandatory
1301 function AbstractValue.render(self, s, scope)
1302 if not self.optional or self.section:has_tabs() or self:cfgvalue(s) or self:formcreated(s) then
1303 scope = scope or {}
1304 scope.section = s
1305 scope.cbid = self:cbid(s)
1306 scope.striptags = luci.util.striptags
1307 scope.pcdata = luci.util.pcdata
1308
1309 scope.ifattr = function(cond,key,val)
1310 if cond then
1311 return string.format(
1312 ' %s="%s"', tostring(key),
1313 luci.util.pcdata(tostring( val
1314 or scope[key]
1315 or (type(self[key]) ~= "function" and self[key])
1316 or "" ))
1317 )
1318 else
1319 return ''
1320 end
1321 end
1322
1323 scope.attr = function(...)
1324 return scope.ifattr( true, ... )
1325 end
1326
1327 Node.render(self, scope)
1328 end
1329 end
1330
1331 -- Return the UCI value of this object
1332 function AbstractValue.cfgvalue(self, section)
1333 local value
1334 if self.tag_error[section] then
1335 value = self:formvalue(section)
1336 else
1337 value = self.map:get(section, self.option)
1338 end
1339
1340 if not value then
1341 return nil
1342 elseif not self.cast or self.cast == type(value) then
1343 return value
1344 elseif self.cast == "string" then
1345 if type(value) == "table" then
1346 return value[1]
1347 end
1348 elseif self.cast == "table" then
1349 return { value }
1350 end
1351 end
1352
1353 -- Validate the form value
1354 function AbstractValue.validate(self, value)
1355 if self.datatype and value then
1356 local args = { }
1357 local dt, ar = self.datatype:match("^(%w+)%(([^%(%)]+)%)")
1358
1359 if dt and ar then
1360 local a
1361 for a in ar:gmatch("[^%s,]+") do
1362 args[#args+1] = a
1363 end
1364 else
1365 dt = self.datatype
1366 end
1367
1368 if dt and datatypes[dt] then
1369 if type(value) == "table" then
1370 local v
1371 for _, v in ipairs(value) do
1372 if v and #v > 0 and not datatypes[dt](v, unpack(args)) then
1373 return nil
1374 end
1375 end
1376 else
1377 if not datatypes[dt](value, unpack(args)) then
1378 return nil
1379 end
1380 end
1381 end
1382 end
1383
1384 return value
1385 end
1386
1387 AbstractValue.transform = AbstractValue.validate
1388
1389
1390 -- Write to UCI
1391 function AbstractValue.write(self, section, value)
1392 return self.map:set(section, self.option, value)
1393 end
1394
1395 -- Remove from UCI
1396 function AbstractValue.remove(self, section)
1397 return self.map:del(section, self.option)
1398 end
1399
1400
1401
1402
1403 --[[
1404 Value - A one-line value
1405 maxlength: The maximum length
1406 ]]--
1407 Value = class(AbstractValue)
1408
1409 function Value.__init__(self, ...)
1410 AbstractValue.__init__(self, ...)
1411 self.template = "cbi/value"
1412 self.keylist = {}
1413 self.vallist = {}
1414 end
1415
1416 function Value.reset_values(self)
1417 self.keylist = {}
1418 self.vallist = {}
1419 end
1420
1421 function Value.value(self, key, val)
1422 val = val or key
1423 table.insert(self.keylist, tostring(key))
1424 table.insert(self.vallist, tostring(val))
1425 end
1426
1427
1428 -- DummyValue - This does nothing except being there
1429 DummyValue = class(AbstractValue)
1430
1431 function DummyValue.__init__(self, ...)
1432 AbstractValue.__init__(self, ...)
1433 self.template = "cbi/dvalue"
1434 self.value = nil
1435 end
1436
1437 function DummyValue.cfgvalue(self, section)
1438 local value
1439 if self.value then
1440 if type(self.value) == "function" then
1441 value = self:value(section)
1442 else
1443 value = self.value
1444 end
1445 else
1446 value = AbstractValue.cfgvalue(self, section)
1447 end
1448 return value
1449 end
1450
1451 function DummyValue.parse(self)
1452
1453 end
1454
1455
1456 --[[
1457 Flag - A flag being enabled or disabled
1458 ]]--
1459 Flag = class(AbstractValue)
1460
1461 function Flag.__init__(self, ...)
1462 AbstractValue.__init__(self, ...)
1463 self.template = "cbi/fvalue"
1464
1465 self.enabled = "1"
1466 self.disabled = "0"
1467 end
1468
1469 -- A flag can only have two states: set or unset
1470 function Flag.parse(self, section)
1471 local fvalue = self:formvalue(section)
1472
1473 if fvalue then
1474 fvalue = self.enabled
1475 else
1476 fvalue = self.disabled
1477 end
1478
1479 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1480 if not(fvalue == self:cfgvalue(section)) then
1481 self:write(section, fvalue)
1482 end
1483 else
1484 self:remove(section)
1485 end
1486 end
1487
1488
1489
1490 --[[
1491 ListValue - A one-line value predefined in a list
1492 widget: The widget that will be used (select, radio)
1493 ]]--
1494 ListValue = class(AbstractValue)
1495
1496 function ListValue.__init__(self, ...)
1497 AbstractValue.__init__(self, ...)
1498 self.template = "cbi/lvalue"
1499
1500 self.keylist = {}
1501 self.vallist = {}
1502 self.size = 1
1503 self.widget = "select"
1504 end
1505
1506 function ListValue.reset_values(self)
1507 self.keylist = {}
1508 self.vallist = {}
1509 end
1510
1511 function ListValue.value(self, key, val, ...)
1512 if luci.util.contains(self.keylist, key) then
1513 return
1514 end
1515
1516 val = val or key
1517 table.insert(self.keylist, tostring(key))
1518 table.insert(self.vallist, tostring(val))
1519
1520 for i, deps in ipairs({...}) do
1521 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1522 end
1523 end
1524
1525 function ListValue.validate(self, val)
1526 if luci.util.contains(self.keylist, val) then
1527 return val
1528 else
1529 return nil
1530 end
1531 end
1532
1533
1534
1535 --[[
1536 MultiValue - Multiple delimited values
1537 widget: The widget that will be used (select, checkbox)
1538 delimiter: The delimiter that will separate the values (default: " ")
1539 ]]--
1540 MultiValue = class(AbstractValue)
1541
1542 function MultiValue.__init__(self, ...)
1543 AbstractValue.__init__(self, ...)
1544 self.template = "cbi/mvalue"
1545
1546 self.keylist = {}
1547 self.vallist = {}
1548
1549 self.widget = "checkbox"
1550 self.delimiter = " "
1551 end
1552
1553 function MultiValue.render(self, ...)
1554 if self.widget == "select" and not self.size then
1555 self.size = #self.vallist
1556 end
1557
1558 AbstractValue.render(self, ...)
1559 end
1560
1561 function MultiValue.reset_values(self)
1562 self.keylist = {}
1563 self.vallist = {}
1564 end
1565
1566 function MultiValue.value(self, key, val)
1567 if luci.util.contains(self.keylist, key) then
1568 return
1569 end
1570
1571 val = val or key
1572 table.insert(self.keylist, tostring(key))
1573 table.insert(self.vallist, tostring(val))
1574 end
1575
1576 function MultiValue.valuelist(self, section)
1577 local val = self:cfgvalue(section)
1578
1579 if not(type(val) == "string") then
1580 return {}
1581 end
1582
1583 return luci.util.split(val, self.delimiter)
1584 end
1585
1586 function MultiValue.validate(self, val)
1587 val = (type(val) == "table") and val or {val}
1588
1589 local result
1590
1591 for i, value in ipairs(val) do
1592 if luci.util.contains(self.keylist, value) then
1593 result = result and (result .. self.delimiter .. value) or value
1594 end
1595 end
1596
1597 return result
1598 end
1599
1600
1601 StaticList = class(MultiValue)
1602
1603 function StaticList.__init__(self, ...)
1604 MultiValue.__init__(self, ...)
1605 self.cast = "table"
1606 self.valuelist = self.cfgvalue
1607
1608 if not self.override_scheme
1609 and self.map:get_scheme(self.section.sectiontype, self.option) then
1610 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1611 if self.value and vs.values and not self.override_values then
1612 for k, v in pairs(vs.values) do
1613 self:value(k, v)
1614 end
1615 end
1616 end
1617 end
1618
1619 function StaticList.validate(self, value)
1620 value = (type(value) == "table") and value or {value}
1621
1622 local valid = {}
1623 for i, v in ipairs(value) do
1624 if luci.util.contains(self.keylist, v) then
1625 table.insert(valid, v)
1626 end
1627 end
1628 return valid
1629 end
1630
1631
1632 DynamicList = class(AbstractValue)
1633
1634 function DynamicList.__init__(self, ...)
1635 AbstractValue.__init__(self, ...)
1636 self.template = "cbi/dynlist"
1637 self.cast = "table"
1638 self.keylist = {}
1639 self.vallist = {}
1640 end
1641
1642 function DynamicList.reset_values(self)
1643 self.keylist = {}
1644 self.vallist = {}
1645 end
1646
1647 function DynamicList.value(self, key, val)
1648 val = val or key
1649 table.insert(self.keylist, tostring(key))
1650 table.insert(self.vallist, tostring(val))
1651 end
1652
1653 function DynamicList.write(self, section, value)
1654 local t = { }
1655
1656 if type(value) == "table" then
1657 local x
1658 for _, x in ipairs(value) do
1659 if x and #x > 0 then
1660 t[#t+1] = x
1661 end
1662 end
1663 else
1664 t = { value }
1665 end
1666
1667 if self.cast == "string" then
1668 value = table.concat(t, " ")
1669 else
1670 value = t
1671 end
1672
1673 return AbstractValue.write(self, section, value)
1674 end
1675
1676 function DynamicList.cfgvalue(self, section)
1677 local value = AbstractValue.cfgvalue(self, section)
1678
1679 if type(value) == "string" then
1680 local x
1681 local t = { }
1682 for x in value:gmatch("%S+") do
1683 if #x > 0 then
1684 t[#t+1] = x
1685 end
1686 end
1687 value = t
1688 end
1689
1690 return value
1691 end
1692
1693 function DynamicList.formvalue(self, section)
1694 local value = AbstractValue.formvalue(self, section)
1695
1696 if type(value) == "string" then
1697 if self.cast == "string" then
1698 local x
1699 local t = { }
1700 for x in value:gmatch("%S+") do
1701 t[#t+1] = x
1702 end
1703 value = t
1704 else
1705 value = { value }
1706 end
1707 end
1708
1709 return value
1710 end
1711
1712
1713 --[[
1714 TextValue - A multi-line value
1715 rows: Rows
1716 ]]--
1717 TextValue = class(AbstractValue)
1718
1719 function TextValue.__init__(self, ...)
1720 AbstractValue.__init__(self, ...)
1721 self.template = "cbi/tvalue"
1722 end
1723
1724 --[[
1725 Button
1726 ]]--
1727 Button = class(AbstractValue)
1728
1729 function Button.__init__(self, ...)
1730 AbstractValue.__init__(self, ...)
1731 self.template = "cbi/button"
1732 self.inputstyle = nil
1733 self.rmempty = true
1734 end
1735
1736
1737 FileUpload = class(AbstractValue)
1738
1739 function FileUpload.__init__(self, ...)
1740 AbstractValue.__init__(self, ...)
1741 self.template = "cbi/upload"
1742 if not self.map.upload_fields then
1743 self.map.upload_fields = { self }
1744 else
1745 self.map.upload_fields[#self.map.upload_fields+1] = self
1746 end
1747 end
1748
1749 function FileUpload.formcreated(self, section)
1750 return AbstractValue.formcreated(self, section) or
1751 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1752 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1753 end
1754
1755 function FileUpload.cfgvalue(self, section)
1756 local val = AbstractValue.cfgvalue(self, section)
1757 if val and fs.access(val) then
1758 return val
1759 end
1760 return nil
1761 end
1762
1763 function FileUpload.formvalue(self, section)
1764 local val = AbstractValue.formvalue(self, section)
1765 if val then
1766 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1767 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1768 then
1769 return val
1770 end
1771 fs.unlink(val)
1772 self.value = nil
1773 end
1774 return nil
1775 end
1776
1777 function FileUpload.remove(self, section)
1778 local val = AbstractValue.formvalue(self, section)
1779 if val and fs.access(val) then fs.unlink(val) end
1780 return AbstractValue.remove(self, section)
1781 end
1782
1783
1784 FileBrowser = class(AbstractValue)
1785
1786 function FileBrowser.__init__(self, ...)
1787 AbstractValue.__init__(self, ...)
1788 self.template = "cbi/browser"
1789 end