libs/cbi: change usage of uvl error api after r5861
[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('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, name)
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:populate_delegator(self)
620 local stat = self.active:parse(false)
621 if stat == FORM_SKIP then
622 return self:parse(...)
623 else
624 return FORM_PROCEED
625 end
626 else
627 return self:parse(...)
628 end
629 end
630 end
631
632 function Delegator.get_next(self, state)
633 for k, v in ipairs(self.chain) do
634 if v == state then
635 return self.chain[k+1]
636 end
637 end
638 end
639
640 function Delegator.get_prev(self, state)
641 for k, v in ipairs(self.chain) do
642 if v == state then
643 return self.chain[k-1]
644 end
645 end
646 end
647
648 function Delegator.get_chain(self)
649 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
650 return type(x) == "table" and x or {x}
651 end
652
653 function Delegator.get_active(self)
654 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
655 end
656
657 --[[
658 Page - A simple node
659 ]]--
660
661 Page = class(Node)
662 Page.__init__ = Node.__init__
663 Page.parse = function() end
664
665
666 --[[
667 SimpleForm - A Simple non-UCI form
668 ]]--
669 SimpleForm = class(Node)
670
671 function SimpleForm.__init__(self, config, title, description, data)
672 Node.__init__(self, title, description)
673 self.config = config
674 self.data = data or {}
675 self.template = "cbi/simpleform"
676 self.dorender = true
677 self.pageaction = false
678 self.readinput = true
679 end
680
681 SimpleForm.formvalue = Map.formvalue
682 SimpleForm.formvaluetable = Map.formvaluetable
683
684 function SimpleForm.parse(self, readinput, ...)
685 self.readinput = (readinput ~= false)
686
687 if self:formvalue("cbi.skip") then
688 return FORM_SKIP
689 end
690
691 if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
692 return FORM_DONE
693 end
694
695 if self:submitstate() then
696 Node.parse(self, 1, ...)
697 end
698
699 local valid = true
700 for k, j in ipairs(self.children) do
701 for i, v in ipairs(j.children) do
702 valid = valid
703 and (not v.tag_missing or not v.tag_missing[1])
704 and (not v.tag_invalid or not v.tag_invalid[1])
705 and (not v.error)
706 end
707 end
708
709 local state =
710 not self:submitstate() and FORM_NODATA
711 or valid and FORM_VALID
712 or FORM_INVALID
713
714 self.dorender = not self.handle
715 if self.handle then
716 local nrender, nstate = self:handle(state, self.data)
717 self.dorender = self.dorender or (nrender ~= false)
718 state = nstate or state
719 end
720 return state
721 end
722
723 function SimpleForm.render(self, ...)
724 if self.dorender then
725 Node.render(self, ...)
726 end
727 end
728
729 function SimpleForm.submitstate(self)
730 return self:formvalue("cbi.submit")
731 end
732
733 function SimpleForm.section(self, class, ...)
734 if instanceof(class, AbstractSection) then
735 local obj = class(self, ...)
736 self:append(obj)
737 return obj
738 else
739 error("class must be a descendent of AbstractSection")
740 end
741 end
742
743 -- Creates a child field
744 function SimpleForm.field(self, class, ...)
745 local section
746 for k, v in ipairs(self.children) do
747 if instanceof(v, SimpleSection) then
748 section = v
749 break
750 end
751 end
752 if not section then
753 section = self:section(SimpleSection)
754 end
755
756 if instanceof(class, AbstractValue) then
757 local obj = class(self, section, ...)
758 obj.track_missing = true
759 section:append(obj)
760 return obj
761 else
762 error("class must be a descendent of AbstractValue")
763 end
764 end
765
766 function SimpleForm.set(self, section, option, value)
767 self.data[option] = value
768 end
769
770
771 function SimpleForm.del(self, section, option)
772 self.data[option] = nil
773 end
774
775
776 function SimpleForm.get(self, section, option)
777 return self.data[option]
778 end
779
780
781 function SimpleForm.get_scheme()
782 return nil
783 end
784
785
786 Form = class(SimpleForm)
787
788 function Form.__init__(self, ...)
789 SimpleForm.__init__(self, ...)
790 self.embedded = true
791 end
792
793
794 --[[
795 AbstractSection
796 ]]--
797 AbstractSection = class(Node)
798
799 function AbstractSection.__init__(self, map, sectiontype, ...)
800 Node.__init__(self, ...)
801 self.sectiontype = sectiontype
802 self.map = map
803 self.config = map.config
804 self.optionals = {}
805 self.defaults = {}
806 self.fields = {}
807 self.tag_error = {}
808 self.tag_invalid = {}
809 self.tag_deperror = {}
810 self.changed = false
811
812 self.optional = true
813 self.addremove = false
814 self.dynamic = false
815 end
816
817 -- Define a tab for the section
818 function AbstractSection.tab(self, tab, title, desc)
819 self.tabs = self.tabs or { }
820 self.tab_names = self.tab_names or { }
821
822 self.tab_names[#self.tab_names+1] = tab
823 self.tabs[tab] = {
824 title = title,
825 description = desc,
826 childs = { }
827 }
828 end
829
830 -- Appends a new option
831 function AbstractSection.option(self, class, option, ...)
832 -- Autodetect from UVL
833 if class == true and self.map:get_scheme(self.sectiontype, option) then
834 local vs = self.map:get_scheme(self.sectiontype, option)
835 if vs.type == "boolean" then
836 class = Flag
837 elseif vs.type == "list" then
838 class = DynamicList
839 elseif vs.type == "enum" or vs.type == "reference" then
840 class = ListValue
841 else
842 class = Value
843 end
844 end
845
846 if instanceof(class, AbstractValue) then
847 local obj = class(self.map, self, option, ...)
848 self:append(obj)
849 self.fields[option] = obj
850 return obj
851 elseif class == true then
852 error("No valid class was given and autodetection failed.")
853 else
854 error("class must be a descendant of AbstractValue")
855 end
856 end
857
858 -- Appends a new tabbed option
859 function AbstractSection.taboption(self, tab, ...)
860
861 assert(tab and self.tabs and self.tabs[tab],
862 "Cannot assign option to not existing tab %q" % tostring(tab))
863
864 local l = self.tabs[tab].childs
865 local o = AbstractSection.option(self, ...)
866
867 if o then l[#l+1] = o end
868
869 return o
870 end
871
872 -- Render a single tab
873 function AbstractSection.render_tab(self, tab, ...)
874
875 assert(tab and self.tabs and self.tabs[tab],
876 "Cannot render not existing tab %q" % tostring(tab))
877
878 for _, node in ipairs(self.tabs[tab].childs) do
879 node:render(...)
880 end
881 end
882
883 -- Parse optional options
884 function AbstractSection.parse_optionals(self, section)
885 if not self.optional then
886 return
887 end
888
889 self.optionals[section] = {}
890
891 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
892 for k,v in ipairs(self.children) do
893 if v.optional and not v:cfgvalue(section) then
894 if field == v.option then
895 field = nil
896 self.map.proceed = true
897 else
898 table.insert(self.optionals[section], v)
899 end
900 end
901 end
902
903 if field and #field > 0 and self.dynamic then
904 self:add_dynamic(field)
905 end
906 end
907
908 -- Add a dynamic option
909 function AbstractSection.add_dynamic(self, field, optional)
910 local o = self:option(Value, field, field)
911 o.optional = optional
912 end
913
914 -- Parse all dynamic options
915 function AbstractSection.parse_dynamic(self, section)
916 if not self.dynamic then
917 return
918 end
919
920 local arr = luci.util.clone(self:cfgvalue(section))
921 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
922 for k, v in pairs(form) do
923 arr[k] = v
924 end
925
926 for key,val in pairs(arr) do
927 local create = true
928
929 for i,c in ipairs(self.children) do
930 if c.option == key then
931 create = false
932 end
933 end
934
935 if create and key:sub(1, 1) ~= "." then
936 self.map.proceed = true
937 self:add_dynamic(key, true)
938 end
939 end
940 end
941
942 -- Returns the section's UCI table
943 function AbstractSection.cfgvalue(self, section)
944 return self.map:get(section)
945 end
946
947 -- Push events
948 function AbstractSection.push_events(self)
949 --luci.util.append(self.map.events, self.events)
950 self.map.changed = true
951 end
952
953 -- Removes the section
954 function AbstractSection.remove(self, section)
955 self.map.proceed = true
956 return self.map:del(section)
957 end
958
959 -- Creates the section
960 function AbstractSection.create(self, section)
961 local stat
962
963 if section then
964 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
965 else
966 section = self.map:add(self.sectiontype)
967 stat = section
968 end
969
970 if stat then
971 for k,v in pairs(self.children) do
972 if v.default then
973 self.map:set(section, v.option, v.default)
974 end
975 end
976
977 for k,v in pairs(self.defaults) do
978 self.map:set(section, k, v)
979 end
980 end
981
982 self.map.proceed = true
983
984 return stat
985 end
986
987
988 SimpleSection = class(AbstractSection)
989
990 function SimpleSection.__init__(self, form, ...)
991 AbstractSection.__init__(self, form, nil, ...)
992 self.template = "cbi/nullsection"
993 end
994
995
996 Table = class(AbstractSection)
997
998 function Table.__init__(self, form, data, ...)
999 local datasource = {}
1000 local tself = self
1001 datasource.config = "table"
1002 self.data = data or {}
1003
1004 datasource.formvalue = Map.formvalue
1005 datasource.formvaluetable = Map.formvaluetable
1006 datasource.readinput = true
1007
1008 function datasource.get(self, section, option)
1009 return tself.data[section] and tself.data[section][option]
1010 end
1011
1012 function datasource.submitstate(self)
1013 return Map.formvalue(self, "cbi.submit")
1014 end
1015
1016 function datasource.del(...)
1017 return true
1018 end
1019
1020 function datasource.get_scheme()
1021 return nil
1022 end
1023
1024 AbstractSection.__init__(self, datasource, "table", ...)
1025 self.template = "cbi/tblsection"
1026 self.rowcolors = true
1027 self.anonymous = true
1028 end
1029
1030 function Table.parse(self, readinput)
1031 self.map.readinput = (readinput ~= false)
1032 for i, k in ipairs(self:cfgsections()) do
1033 if self.map:submitstate() then
1034 Node.parse(self, k)
1035 end
1036 end
1037 end
1038
1039 function Table.cfgsections(self)
1040 local sections = {}
1041
1042 for i, v in luci.util.kspairs(self.data) do
1043 table.insert(sections, i)
1044 end
1045
1046 return sections
1047 end
1048
1049 function Table.update(self, data)
1050 self.data = data
1051 end
1052
1053
1054
1055 --[[
1056 NamedSection - A fixed configuration section defined by its name
1057 ]]--
1058 NamedSection = class(AbstractSection)
1059
1060 function NamedSection.__init__(self, map, section, stype, ...)
1061 AbstractSection.__init__(self, map, stype, ...)
1062
1063 -- Defaults
1064 self.addremove = false
1065
1066 -- Use defaults from UVL
1067 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1068 local vs = self.map:get_scheme(self.sectiontype)
1069 self.addremove = not vs.unique and not vs.required
1070 self.dynamic = vs.dynamic
1071 self.title = self.title or vs.title
1072 self.description = self.description or vs.descr
1073 end
1074
1075 self.template = "cbi/nsection"
1076 self.section = section
1077 end
1078
1079 function NamedSection.parse(self, novld)
1080 local s = self.section
1081 local active = self:cfgvalue(s)
1082
1083 if self.addremove then
1084 local path = self.config.."."..s
1085 if active then -- Remove the section
1086 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1087 self:push_events()
1088 return
1089 end
1090 else -- Create and apply default values
1091 if self.map:formvalue("cbi.cns."..path) then
1092 self:create(s)
1093 return
1094 end
1095 end
1096 end
1097
1098 if active then
1099 AbstractSection.parse_dynamic(self, s)
1100 if self.map:submitstate() then
1101 Node.parse(self, s)
1102
1103 if not novld and not self.override_scheme and self.map.scheme then
1104 _uvl_validate_section(self, s)
1105 end
1106 end
1107 AbstractSection.parse_optionals(self, s)
1108
1109 if self.changed then
1110 self:push_events()
1111 end
1112 end
1113 end
1114
1115
1116 --[[
1117 TypedSection - A (set of) configuration section(s) defined by the type
1118 addremove: Defines whether the user can add/remove sections of this type
1119 anonymous: Allow creating anonymous sections
1120 validate: a validation function returning nil if the section is invalid
1121 ]]--
1122 TypedSection = class(AbstractSection)
1123
1124 function TypedSection.__init__(self, map, type, ...)
1125 AbstractSection.__init__(self, map, type, ...)
1126
1127 self.template = "cbi/tsection"
1128 self.deps = {}
1129 self.anonymous = false
1130
1131 -- Use defaults from UVL
1132 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1133 local vs = self.map:get_scheme(self.sectiontype)
1134 self.addremove = not vs.unique and not vs.required
1135 self.dynamic = vs.dynamic
1136 self.anonymous = not vs.named
1137 self.title = self.title or vs.title
1138 self.description = self.description or vs.descr
1139 end
1140 end
1141
1142 -- Return all matching UCI sections for this TypedSection
1143 function TypedSection.cfgsections(self)
1144 local sections = {}
1145 self.map.uci:foreach(self.map.config, self.sectiontype,
1146 function (section)
1147 if self:checkscope(section[".name"]) then
1148 table.insert(sections, section[".name"])
1149 end
1150 end)
1151
1152 return sections
1153 end
1154
1155 -- Limits scope to sections that have certain option => value pairs
1156 function TypedSection.depends(self, option, value)
1157 table.insert(self.deps, {option=option, value=value})
1158 end
1159
1160 function TypedSection.parse(self, novld)
1161 if self.addremove then
1162 -- Remove
1163 local crval = REMOVE_PREFIX .. self.config
1164 local name = self.map:formvaluetable(crval)
1165 for k,v in pairs(name) do
1166 if k:sub(-2) == ".x" then
1167 k = k:sub(1, #k - 2)
1168 end
1169 if self:cfgvalue(k) and self:checkscope(k) then
1170 self:remove(k)
1171 end
1172 end
1173 end
1174
1175 local co
1176 for i, k in ipairs(self:cfgsections()) do
1177 AbstractSection.parse_dynamic(self, k)
1178 if self.map:submitstate() then
1179 Node.parse(self, k, novld)
1180
1181 if not novld and not self.override_scheme and self.map.scheme then
1182 _uvl_validate_section(self, k)
1183 end
1184 end
1185 AbstractSection.parse_optionals(self, k)
1186 end
1187
1188 if self.addremove then
1189 -- Create
1190 local created
1191 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1192 local name = self.map:formvalue(crval)
1193 if self.anonymous then
1194 if name then
1195 created = self:create()
1196 end
1197 else
1198 if name then
1199 -- Ignore if it already exists
1200 if self:cfgvalue(name) then
1201 name = nil;
1202 end
1203
1204 name = self:checkscope(name)
1205
1206 if not name then
1207 self.err_invalid = true
1208 end
1209
1210 if name and #name > 0 then
1211 created = self:create(name) and name
1212 if not created then
1213 self.invalid_cts = true
1214 end
1215 end
1216 end
1217 end
1218
1219 if created then
1220 AbstractSection.parse_optionals(self, created)
1221 end
1222 end
1223
1224 if created or self.changed then
1225 self:push_events()
1226 end
1227 end
1228
1229 -- Verifies scope of sections
1230 function TypedSection.checkscope(self, section)
1231 -- Check if we are not excluded
1232 if self.filter and not self:filter(section) then
1233 return nil
1234 end
1235
1236 -- Check if at least one dependency is met
1237 if #self.deps > 0 and self:cfgvalue(section) then
1238 local stat = false
1239
1240 for k, v in ipairs(self.deps) do
1241 if self:cfgvalue(section)[v.option] == v.value then
1242 stat = true
1243 end
1244 end
1245
1246 if not stat then
1247 return nil
1248 end
1249 end
1250
1251 return self:validate(section)
1252 end
1253
1254
1255 -- Dummy validate function
1256 function TypedSection.validate(self, section)
1257 return section
1258 end
1259
1260
1261 --[[
1262 AbstractValue - An abstract Value Type
1263 null: Value can be empty
1264 valid: A function returning the value if it is valid otherwise nil
1265 depends: A table of option => value pairs of which one must be true
1266 default: The default value
1267 size: The size of the input fields
1268 rmempty: Unset value if empty
1269 optional: This value is optional (see AbstractSection.optionals)
1270 ]]--
1271 AbstractValue = class(Node)
1272
1273 function AbstractValue.__init__(self, map, section, option, ...)
1274 Node.__init__(self, ...)
1275 self.section = section
1276 self.option = option
1277 self.map = map
1278 self.config = map.config
1279 self.tag_invalid = {}
1280 self.tag_missing = {}
1281 self.tag_reqerror = {}
1282 self.tag_error = {}
1283 self.deps = {}
1284 self.subdeps = {}
1285 --self.cast = "string"
1286
1287 self.track_missing = false
1288 self.rmempty = true
1289 self.default = nil
1290 self.size = nil
1291 self.optional = false
1292 end
1293
1294 function AbstractValue.prepare(self)
1295 -- Use defaults from UVL
1296 if not self.override_scheme
1297 and self.map:get_scheme(self.section.sectiontype, self.option) then
1298 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1299 if self.cast == nil then
1300 self.cast = (vs.type == "list") and "list" or "string"
1301 end
1302 self.title = self.title or vs.title
1303 self.description = self.description or vs.descr
1304 if self.default == nil then
1305 self.default = vs.default
1306 end
1307
1308 if vs.depends and not self.override_dependencies then
1309 for i, deps in ipairs(vs.depends) do
1310 deps = _uvl_strip_remote_dependencies(deps)
1311 if next(deps) then
1312 self:depends(deps)
1313 end
1314 end
1315 end
1316 end
1317
1318 self.cast = self.cast or "string"
1319 end
1320
1321 -- Add a dependencie to another section field
1322 function AbstractValue.depends(self, field, value)
1323 local deps
1324 if type(field) == "string" then
1325 deps = {}
1326 deps[field] = value
1327 else
1328 deps = field
1329 end
1330
1331 table.insert(self.deps, {deps=deps, add=""})
1332 end
1333
1334 -- Generates the unique CBID
1335 function AbstractValue.cbid(self, section)
1336 return "cbid."..self.map.config.."."..section.."."..self.option
1337 end
1338
1339 -- Return whether this object should be created
1340 function AbstractValue.formcreated(self, section)
1341 local key = "cbi.opt."..self.config.."."..section
1342 return (self.map:formvalue(key) == self.option)
1343 end
1344
1345 -- Returns the formvalue for this object
1346 function AbstractValue.formvalue(self, section)
1347 return self.map:formvalue(self:cbid(section))
1348 end
1349
1350 function AbstractValue.additional(self, value)
1351 self.optional = value
1352 end
1353
1354 function AbstractValue.mandatory(self, value)
1355 self.rmempty = not value
1356 end
1357
1358 function AbstractValue.parse(self, section, novld)
1359 local fvalue = self:formvalue(section)
1360 local cvalue = self:cfgvalue(section)
1361
1362 -- If favlue and cvalue are both tables and have the same content
1363 -- make them identical
1364 if type(fvalue) == "table" and type(cvalue) == "table" then
1365 local equal = #fvalue == #cvalue
1366 if equal then
1367 for i=1, #fvalue do
1368 if cvalue[i] ~= fvalue[i] then
1369 equal = false
1370 end
1371 end
1372 end
1373 if equal then
1374 fvalue = cvalue
1375 end
1376 end
1377
1378 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1379 fvalue = self:transform(self:validate(fvalue, section))
1380 if not fvalue and not novld then
1381 if self.error then
1382 self.error[section] = "invalid"
1383 else
1384 self.error = { [section] = "invalid" }
1385 end
1386 if self.section.error then
1387 table.insert(self.section.error[section], "invalid")
1388 else
1389 self.section.error = {[section] = {"invalid"}}
1390 end
1391 self.map.save = false
1392 end
1393 if fvalue and not (fvalue == cvalue) then
1394 if self:write(section, fvalue) then
1395 -- Push events
1396 self.section.changed = true
1397 --luci.util.append(self.map.events, self.events)
1398 end
1399 end
1400 else -- Unset the UCI or error
1401 if self.rmempty or self.optional then
1402 if self:remove(section) then
1403 -- Push events
1404 self.section.changed = true
1405 --luci.util.append(self.map.events, self.events)
1406 end
1407 elseif cvalue ~= fvalue and not novld then
1408 self:write(section, fvalue or "")
1409 if self.error then
1410 self.error[section] = "missing"
1411 else
1412 self.error = { [section] = "missing" }
1413 end
1414 self.map.save = false
1415 end
1416 end
1417 end
1418
1419 -- Render if this value exists or if it is mandatory
1420 function AbstractValue.render(self, s, scope)
1421 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1422 scope = scope or {}
1423 scope.section = s
1424 scope.cbid = self:cbid(s)
1425 scope.striptags = luci.util.striptags
1426 scope.pcdata = luci.util.pcdata
1427
1428 scope.ifattr = function(cond,key,val)
1429 if cond then
1430 return string.format(
1431 ' %s="%s"', tostring(key),
1432 luci.util.pcdata(tostring( val
1433 or scope[key]
1434 or (type(self[key]) ~= "function" and self[key])
1435 or "" ))
1436 )
1437 else
1438 return ''
1439 end
1440 end
1441
1442 scope.attr = function(...)
1443 return scope.ifattr( true, ... )
1444 end
1445
1446 Node.render(self, scope)
1447 end
1448 end
1449
1450 -- Return the UCI value of this object
1451 function AbstractValue.cfgvalue(self, section)
1452 local value = self.map:get(section, self.option)
1453 if not value then
1454 return nil
1455 elseif not self.cast or self.cast == type(value) then
1456 return value
1457 elseif self.cast == "string" then
1458 if type(value) == "table" then
1459 return value[1]
1460 end
1461 elseif self.cast == "table" then
1462 return luci.util.split(value, "%s+", nil, true)
1463 end
1464 end
1465
1466 -- Validate the form value
1467 function AbstractValue.validate(self, value)
1468 return value
1469 end
1470
1471 AbstractValue.transform = AbstractValue.validate
1472
1473
1474 -- Write to UCI
1475 function AbstractValue.write(self, section, value)
1476 return self.map:set(section, self.option, value)
1477 end
1478
1479 -- Remove from UCI
1480 function AbstractValue.remove(self, section)
1481 return self.map:del(section, self.option)
1482 end
1483
1484
1485
1486
1487 --[[
1488 Value - A one-line value
1489 maxlength: The maximum length
1490 ]]--
1491 Value = class(AbstractValue)
1492
1493 function Value.__init__(self, ...)
1494 AbstractValue.__init__(self, ...)
1495 self.template = "cbi/value"
1496 self.keylist = {}
1497 self.vallist = {}
1498 end
1499
1500 function Value.value(self, key, val)
1501 val = val or key
1502 table.insert(self.keylist, tostring(key))
1503 table.insert(self.vallist, tostring(val))
1504 end
1505
1506
1507 -- DummyValue - This does nothing except being there
1508 DummyValue = class(AbstractValue)
1509
1510 function DummyValue.__init__(self, ...)
1511 AbstractValue.__init__(self, ...)
1512 self.template = "cbi/dvalue"
1513 self.value = nil
1514 end
1515
1516 function DummyValue.cfgvalue(self, section)
1517 local value
1518 if self.value then
1519 if type(self.value) == "function" then
1520 value = self:value(section)
1521 else
1522 value = self.value
1523 end
1524 else
1525 value = AbstractValue.cfgvalue(self, section)
1526 end
1527 return value
1528 end
1529
1530 function DummyValue.parse(self)
1531
1532 end
1533
1534
1535 --[[
1536 Flag - A flag being enabled or disabled
1537 ]]--
1538 Flag = class(AbstractValue)
1539
1540 function Flag.__init__(self, ...)
1541 AbstractValue.__init__(self, ...)
1542 self.template = "cbi/fvalue"
1543
1544 self.enabled = "1"
1545 self.disabled = "0"
1546 end
1547
1548 -- A flag can only have two states: set or unset
1549 function Flag.parse(self, section)
1550 local fvalue = self:formvalue(section)
1551
1552 if fvalue then
1553 fvalue = self.enabled
1554 else
1555 fvalue = self.disabled
1556 end
1557
1558 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1559 if not(fvalue == self:cfgvalue(section)) then
1560 self:write(section, fvalue)
1561 end
1562 else
1563 self:remove(section)
1564 end
1565 end
1566
1567
1568
1569 --[[
1570 ListValue - A one-line value predefined in a list
1571 widget: The widget that will be used (select, radio)
1572 ]]--
1573 ListValue = class(AbstractValue)
1574
1575 function ListValue.__init__(self, ...)
1576 AbstractValue.__init__(self, ...)
1577 self.template = "cbi/lvalue"
1578
1579 self.keylist = {}
1580 self.vallist = {}
1581 self.size = 1
1582 self.widget = "select"
1583 end
1584
1585 function ListValue.prepare(self, ...)
1586 AbstractValue.prepare(self, ...)
1587 if not self.override_scheme
1588 and self.map:get_scheme(self.section.sectiontype, self.option) then
1589 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1590 if self.value and vs.valuelist and not self.override_values then
1591 for k, v in ipairs(vs.valuelist) do
1592 local deps = {}
1593 if not self.override_dependencies
1594 and vs.enum_depends and vs.enum_depends[v.value] then
1595 for i, dep in ipairs(vs.enum_depends[v.value]) do
1596 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1597 end
1598 end
1599 self:value(v.value, v.title or v.value, unpack(deps))
1600 end
1601 end
1602 end
1603 end
1604
1605 function ListValue.value(self, key, val, ...)
1606 if luci.util.contains(self.keylist, key) then
1607 return
1608 end
1609
1610 val = val or key
1611 table.insert(self.keylist, tostring(key))
1612 table.insert(self.vallist, tostring(val))
1613
1614 for i, deps in ipairs({...}) do
1615 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1616 end
1617 end
1618
1619 function ListValue.validate(self, val)
1620 if luci.util.contains(self.keylist, val) then
1621 return val
1622 else
1623 return nil
1624 end
1625 end
1626
1627
1628
1629 --[[
1630 MultiValue - Multiple delimited values
1631 widget: The widget that will be used (select, checkbox)
1632 delimiter: The delimiter that will separate the values (default: " ")
1633 ]]--
1634 MultiValue = class(AbstractValue)
1635
1636 function MultiValue.__init__(self, ...)
1637 AbstractValue.__init__(self, ...)
1638 self.template = "cbi/mvalue"
1639
1640 self.keylist = {}
1641 self.vallist = {}
1642
1643 self.widget = "checkbox"
1644 self.delimiter = " "
1645 end
1646
1647 function MultiValue.render(self, ...)
1648 if self.widget == "select" and not self.size then
1649 self.size = #self.vallist
1650 end
1651
1652 AbstractValue.render(self, ...)
1653 end
1654
1655 function MultiValue.value(self, key, val)
1656 if luci.util.contains(self.keylist, key) then
1657 return
1658 end
1659
1660 val = val or key
1661 table.insert(self.keylist, tostring(key))
1662 table.insert(self.vallist, tostring(val))
1663 end
1664
1665 function MultiValue.valuelist(self, section)
1666 local val = self:cfgvalue(section)
1667
1668 if not(type(val) == "string") then
1669 return {}
1670 end
1671
1672 return luci.util.split(val, self.delimiter)
1673 end
1674
1675 function MultiValue.validate(self, val)
1676 val = (type(val) == "table") and val or {val}
1677
1678 local result
1679
1680 for i, value in ipairs(val) do
1681 if luci.util.contains(self.keylist, value) then
1682 result = result and (result .. self.delimiter .. value) or value
1683 end
1684 end
1685
1686 return result
1687 end
1688
1689
1690 StaticList = class(MultiValue)
1691
1692 function StaticList.__init__(self, ...)
1693 MultiValue.__init__(self, ...)
1694 self.cast = "table"
1695 self.valuelist = self.cfgvalue
1696
1697 if not self.override_scheme
1698 and self.map:get_scheme(self.section.sectiontype, self.option) then
1699 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1700 if self.value and vs.values and not self.override_values then
1701 for k, v in pairs(vs.values) do
1702 self:value(k, v)
1703 end
1704 end
1705 end
1706 end
1707
1708 function StaticList.validate(self, value)
1709 value = (type(value) == "table") and value or {value}
1710
1711 local valid = {}
1712 for i, v in ipairs(value) do
1713 if luci.util.contains(self.keylist, v) then
1714 table.insert(valid, v)
1715 end
1716 end
1717 return valid
1718 end
1719
1720
1721 DynamicList = class(AbstractValue)
1722
1723 function DynamicList.__init__(self, ...)
1724 AbstractValue.__init__(self, ...)
1725 self.template = "cbi/dynlist"
1726 self.cast = "table"
1727 self.keylist = {}
1728 self.vallist = {}
1729 end
1730
1731 function DynamicList.value(self, key, val)
1732 val = val or key
1733 table.insert(self.keylist, tostring(key))
1734 table.insert(self.vallist, tostring(val))
1735 end
1736
1737 function DynamicList.write(self, ...)
1738 self.map.proceed = true
1739 return AbstractValue.write(self, ...)
1740 end
1741
1742 function DynamicList.formvalue(self, section)
1743 local value = AbstractValue.formvalue(self, section)
1744 value = (type(value) == "table") and value or {value}
1745
1746 local valid = {}
1747 for i, v in ipairs(value) do
1748 if v and #v > 0
1749 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1750 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1751 table.insert(valid, v)
1752 end
1753 end
1754
1755 return valid
1756 end
1757
1758
1759 --[[
1760 TextValue - A multi-line value
1761 rows: Rows
1762 ]]--
1763 TextValue = class(AbstractValue)
1764
1765 function TextValue.__init__(self, ...)
1766 AbstractValue.__init__(self, ...)
1767 self.template = "cbi/tvalue"
1768 end
1769
1770 --[[
1771 Button
1772 ]]--
1773 Button = class(AbstractValue)
1774
1775 function Button.__init__(self, ...)
1776 AbstractValue.__init__(self, ...)
1777 self.template = "cbi/button"
1778 self.inputstyle = nil
1779 self.rmempty = true
1780 end
1781
1782
1783 FileUpload = class(AbstractValue)
1784
1785 function FileUpload.__init__(self, ...)
1786 AbstractValue.__init__(self, ...)
1787 self.template = "cbi/upload"
1788 if not self.map.upload_fields then
1789 self.map.upload_fields = { self }
1790 else
1791 self.map.upload_fields[#self.map.upload_fields+1] = self
1792 end
1793 end
1794
1795 function FileUpload.formcreated(self, section)
1796 return AbstractValue.formcreated(self, section) or
1797 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1798 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1799 end
1800
1801 function FileUpload.cfgvalue(self, section)
1802 local val = AbstractValue.cfgvalue(self, section)
1803 if val and fs.access(val) then
1804 return val
1805 end
1806 return nil
1807 end
1808
1809 function FileUpload.formvalue(self, section)
1810 local val = AbstractValue.formvalue(self, section)
1811 if val then
1812 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1813 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1814 then
1815 return val
1816 end
1817 fs.unlink(val)
1818 self.value = nil
1819 end
1820 return nil
1821 end
1822
1823 function FileUpload.remove(self, section)
1824 local val = AbstractValue.formvalue(self, section)
1825 if val and fs.access(val) then fs.unlink(val) end
1826 return AbstractValue.remove(self, section)
1827 end
1828
1829
1830 FileBrowser = class(AbstractValue)
1831
1832 function FileBrowser.__init__(self, ...)
1833 AbstractValue.__init__(self, ...)
1834 self.template = "cbi/browser"
1835 end