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