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