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