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