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