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