root/luci/trunk/libs/cbi/luasrc/cbi.lua @ 5556

Revision 5556, 39.4 KB (checked in by Cyrus, 4 years ago)

CBI: Allow lazy loading of wizard pages (improving RAM usage)

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