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