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