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