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