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

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

NIU: More pages

  • 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    if type(node) == "table" and getmetatable(node) == nil then
523        node = Compound(unpack(node))
524    end
525    assert(type(node) == "function" or instanceof(node, Compound), "Invalid")
526    assert(not self.nodes[name], "Duplicate entry")
527
528    self.nodes[name] = node
529end
530
531function Delegator.add(self, name, node)
532    node = self:set(name, node)
533    self.defaultpath[#self.defaultpath+1] = name
534end
535
536function Delegator.insert_after(self, name, after)
537    local n = #self.chain + 1
538    for k, v in ipairs(self.chain) do
539        if v == after then
540            n = k + 1
541            break
542        end
543    end
544    table.insert(self.chain, n, name)
545end
546
547function Delegator.set_route(self, ...)
548    local n, chain, route = 0, self.chain, {...}
549    for i = 1, #chain do
550        if chain[i] == self.current then
551            n = i
552            break
553        end
554    end
555    for i = 1, #route do
556        n = n + 1
557        chain[n] = route[i]
558    end
559    for i = n + 1, #chain do
560        chain[i] = nil
561    end
562end
563
564function Delegator.get(self, name)
565    return self.nodes[name]
566end
567
568function Delegator.parse(self, ...)
569    if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
570        if self:_run_hooks("on_cancel") then
571            return FORM_DONE
572        end
573    end
574   
575    if not Map.formvalue(self, "cbi.delg.current") then
576        self:_run_hooks("on_init")
577    end
578
579    local newcurrent
580    self.chain = self.chain or self:get_chain()
581    self.current = self.current or self:get_active()
582    self.active = self.active or self:get(self.current)
583    assert(self.active, "Invalid state")
584   
585    local stat = FORM_DONE
586    if type(self.active) ~= "function" then
587        self.active:populate_delegator(self)
588        stat = self.active:parse() 
589    else
590        self:active()
591    end
592
593    if stat > FORM_PROCEED then
594        if Map.formvalue(self, "cbi.delg.back") then
595            newcurrent = self:get_prev(self.current)
596        else
597            newcurrent = self:get_next(self.current)
598        end
599    elseif stat < FORM_PROCEED then
600        return stat
601    end
602   
603
604    if not Map.formvalue(self, "cbi.submit") then
605        return FORM_NODATA
606    elseif stat > FORM_PROCEED
607    and (not newcurrent or not self:get(newcurrent)) then
608        return self:_run_hook("on_done") or FORM_DONE
609    else
610        self.current = newcurrent or self.current
611        self.active = self:get(self.current)
612        if type(self.active) ~= "function" then
613            self.active:parse(false)
614            return FROM_PROCEED
615        else
616            return self:parse(...)
617        end
618    end
619end
620
621function Delegator.get_next(self, state)
622    for k, v in ipairs(self.chain) do
623        if v == state then
624            return self.chain[k+1]
625        end
626    end
627end
628
629function Delegator.get_prev(self, state)
630    for k, v in ipairs(self.chain) do
631        if v == state then
632            return self.chain[k-1]
633        end
634    end
635end
636
637function Delegator.get_chain(self)
638    local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
639    return type(x) == "table" and x or {x}
640end
641
642function Delegator.get_active(self)
643    return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
644end
645
646--[[
647Page - A simple node
648]]--
649
650Page = class(Node)
651Page.__init__ = Node.__init__
652Page.parse    = function() end
653
654
655--[[
656SimpleForm - A Simple non-UCI form
657]]--
658SimpleForm = class(Node)
659
660function SimpleForm.__init__(self, config, title, description, data)
661    Node.__init__(self, title, description)
662    self.config = config
663    self.data = data or {}
664    self.template = "cbi/simpleform"
665    self.dorender = true
666    self.pageaction = false
667    self.readinput = true
668end
669
670SimpleForm.formvalue = Map.formvalue
671SimpleForm.formvaluetable = Map.formvaluetable
672
673function SimpleForm.parse(self, readinput, ...)
674    self.readinput = (readinput ~= false)
675
676    if self:formvalue("cbi.skip") then
677        return FORM_SKIP
678    end
679
680    if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
681        return FORM_DONE
682    end
683
684    if self:submitstate() then
685        Node.parse(self, 1, ...)
686    end
687
688    local valid = true
689    for k, j in ipairs(self.children) do
690        for i, v in ipairs(j.children) do
691            valid = valid
692             and (not v.tag_missing or not v.tag_missing[1])
693             and (not v.tag_invalid or not v.tag_invalid[1])
694             and (not v.error)
695        end
696    end
697
698    local state =
699        not self:submitstate() and FORM_NODATA
700        or valid and FORM_VALID
701        or FORM_INVALID
702
703    self.dorender = not self.handle
704    if self.handle then
705        local nrender, nstate = self:handle(state, self.data)
706        self.dorender = self.dorender or (nrender ~= false)
707        state = nstate or state
708    end
709    return state
710end
711
712function SimpleForm.render(self, ...)
713    if self.dorender then
714        Node.render(self, ...)
715    end
716end
717
718function SimpleForm.submitstate(self)
719    return self:formvalue("cbi.submit")
720end
721
722function SimpleForm.section(self, class, ...)
723    if instanceof(class, AbstractSection) then
724        local obj  = class(self, ...)
725        self:append(obj)
726        return obj
727    else
728        error("class must be a descendent of AbstractSection")
729    end
730end
731
732-- Creates a child field
733function SimpleForm.field(self, class, ...)
734    local section
735    for k, v in ipairs(self.children) do
736        if instanceof(v, SimpleSection) then
737            section = v
738            break
739        end
740    end
741    if not section then
742        section = self:section(SimpleSection)
743    end
744
745    if instanceof(class, AbstractValue) then
746        local obj  = class(self, section, ...)
747        obj.track_missing = true
748        section:append(obj)
749        return obj
750    else
751        error("class must be a descendent of AbstractValue")
752    end
753end
754
755function SimpleForm.set(self, section, option, value)
756    self.data[option] = value
757end
758
759
760function SimpleForm.del(self, section, option)
761    self.data[option] = nil
762end
763
764
765function SimpleForm.get(self, section, option)
766    return self.data[option]
767end
768
769
770function SimpleForm.get_scheme()
771    return nil
772end
773
774
775Form = class(SimpleForm)
776
777function Form.__init__(self, ...)
778    SimpleForm.__init__(self, ...)
779    self.embedded = true
780end
781
782
783--[[
784AbstractSection
785]]--
786AbstractSection = class(Node)
787
788function AbstractSection.__init__(self, map, sectiontype, ...)
789    Node.__init__(self, ...)
790    self.sectiontype = sectiontype
791    self.map = map
792    self.config = map.config
793    self.optionals = {}
794    self.defaults = {}
795    self.fields = {}
796    self.tag_error = {}
797    self.tag_invalid = {}
798    self.tag_deperror = {}
799    self.changed = false
800
801    self.optional = true
802    self.addremove = false
803    self.dynamic = false
804end
805
806-- Define a tab for the section
807function AbstractSection.tab(self, tab, title, desc)
808    self.tabs      = self.tabs      or { }
809    self.tab_names = self.tab_names or { }
810
811    self.tab_names[#self.tab_names+1] = tab
812    self.tabs[tab] = {
813        title       = title,
814        description = desc,
815        childs      = { }
816    }
817end
818
819-- Appends a new option
820function AbstractSection.option(self, class, option, ...)
821    -- Autodetect from UVL
822    if class == true and self.map:get_scheme(self.sectiontype, option) then
823        local vs = self.map:get_scheme(self.sectiontype, option)
824        if vs.type == "boolean" then
825            class = Flag
826        elseif vs.type == "list" then
827            class = DynamicList
828        elseif vs.type == "enum" or vs.type == "reference" then
829            class = ListValue
830        else
831            class = Value
832        end
833    end
834
835    if instanceof(class, AbstractValue) then
836        local obj  = class(self.map, self, option, ...)
837        self:append(obj)
838        self.fields[option] = obj
839        return obj
840    elseif class == true then
841        error("No valid class was given and autodetection failed.")
842    else
843        error("class must be a descendant of AbstractValue")
844    end
845end
846
847-- Appends a new tabbed option
848function AbstractSection.taboption(self, tab, ...)
849
850    assert(tab and self.tabs and self.tabs[tab],
851        "Cannot assign option to not existing tab %q" % tostring(tab))
852
853    local l = self.tabs[tab].childs
854    local o = AbstractSection.option(self, ...)
855
856    if o then l[#l+1] = o end
857
858    return o
859end
860
861-- Render a single tab
862function AbstractSection.render_tab(self, tab, ...)
863
864    assert(tab and self.tabs and self.tabs[tab],
865        "Cannot render not existing tab %q" % tostring(tab))
866
867    for _, node in ipairs(self.tabs[tab].childs) do
868        node:render(...)
869    end
870end
871
872-- Parse optional options
873function AbstractSection.parse_optionals(self, section)
874    if not self.optional then
875        return
876    end
877
878    self.optionals[section] = {}
879
880    local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
881    for k,v in ipairs(self.children) do
882        if v.optional and not v:cfgvalue(section) then
883            if field == v.option then
884                field = nil
885                self.map.proceed = true
886            else
887                table.insert(self.optionals[section], v)
888            end
889        end
890    end
891
892    if field and #field > 0 and self.dynamic then
893        self:add_dynamic(field)
894    end
895end
896
897-- Add a dynamic option
898function AbstractSection.add_dynamic(self, field, optional)
899    local o = self:option(Value, field, field)
900    o.optional = optional
901end
902
903-- Parse all dynamic options
904function AbstractSection.parse_dynamic(self, section)
905    if not self.dynamic then
906        return
907    end
908
909    local arr  = luci.util.clone(self:cfgvalue(section))
910    local form = self.map:formvaluetable("cbid."..self.config.."."..section)
911    for k, v in pairs(form) do
912        arr[k] = v
913    end
914
915    for key,val in pairs(arr) do
916        local create = true
917
918        for i,c in ipairs(self.children) do
919            if c.option == key then
920                create = false
921            end
922        end
923
924        if create and key:sub(1, 1) ~= "." then
925            self.map.proceed = true
926            self:add_dynamic(key, true)
927        end
928    end
929end
930
931-- Returns the section's UCI table
932function AbstractSection.cfgvalue(self, section)
933    return self.map:get(section)
934end
935
936-- Push events
937function AbstractSection.push_events(self)
938    --luci.util.append(self.map.events, self.events)
939    self.map.changed = true
940end
941
942-- Removes the section
943function AbstractSection.remove(self, section)
944    self.map.proceed = true
945    return self.map:del(section)
946end
947
948-- Creates the section
949function AbstractSection.create(self, section)
950    local stat
951
952    if section then
953        stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
954    else
955        section = self.map:add(self.sectiontype)
956        stat = section
957    end
958
959    if stat then
960        for k,v in pairs(self.children) do
961            if v.default then
962                self.map:set(section, v.option, v.default)
963            end
964        end
965
966        for k,v in pairs(self.defaults) do
967            self.map:set(section, k, v)
968        end
969    end
970
971    self.map.proceed = true
972
973    return stat
974end
975
976
977SimpleSection = class(AbstractSection)
978
979function SimpleSection.__init__(self, form, ...)
980    AbstractSection.__init__(self, form, nil, ...)
981    self.template = "cbi/nullsection"
982end
983
984
985Table = class(AbstractSection)
986
987function Table.__init__(self, form, data, ...)
988    local datasource = {}
989    local tself = self
990    datasource.config = "table"
991    self.data = data or {}
992
993    datasource.formvalue = Map.formvalue
994    datasource.formvaluetable = Map.formvaluetable
995    datasource.readinput = true
996
997    function datasource.get(self, section, option)
998        return tself.data[section] and tself.data[section][option]
999    end
1000
1001    function datasource.submitstate(self)
1002        return Map.formvalue(self, "cbi.submit")
1003    end
1004
1005    function datasource.del(...)
1006        return true
1007    end
1008
1009    function datasource.get_scheme()
1010        return nil
1011    end
1012
1013    AbstractSection.__init__(self, datasource, "table", ...)
1014    self.template = "cbi/tblsection"
1015    self.rowcolors = true
1016    self.anonymous = true
1017end
1018
1019function Table.parse(self, readinput)
1020    self.map.readinput = (readinput ~= false)
1021    for i, k in ipairs(self:cfgsections()) do
1022        if self.map:submitstate() then
1023            Node.parse(self, k)
1024        end
1025    end
1026end
1027
1028function Table.cfgsections(self)
1029    local sections = {}
1030
1031    for i, v in luci.util.kspairs(self.data) do
1032        table.insert(sections, i)
1033    end
1034
1035    return sections
1036end
1037
1038function Table.update(self, data)
1039    self.data = data
1040end
1041
1042
1043
1044--[[
1045NamedSection - A fixed configuration section defined by its name
1046]]--
1047NamedSection = class(AbstractSection)
1048
1049function NamedSection.__init__(self, map, section, stype, ...)
1050    AbstractSection.__init__(self, map, stype, ...)
1051
1052    -- Defaults
1053    self.addremove = false
1054
1055    -- Use defaults from UVL
1056    if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1057        local vs = self.map:get_scheme(self.sectiontype)
1058        self.addremove = not vs.unique and not vs.required
1059        self.dynamic   = vs.dynamic
1060        self.title       = self.title or vs.title
1061        self.description = self.description or vs.descr
1062    end
1063
1064    self.template = "cbi/nsection"
1065    self.section = section
1066end
1067
1068function NamedSection.parse(self, novld)
1069    local s = self.section
1070    local active = self:cfgvalue(s)
1071
1072    if self.addremove then
1073        local path = self.config.."."..s
1074        if active then -- Remove the section
1075            if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1076                self:push_events()
1077                return
1078            end
1079        else           -- Create and apply default values
1080            if self.map:formvalue("cbi.cns."..path) then
1081                self:create(s)
1082                return
1083            end
1084        end
1085    end
1086
1087    if active then
1088        AbstractSection.parse_dynamic(self, s)
1089        if self.map:submitstate() then
1090            Node.parse(self, s)
1091
1092            if not novld and not self.override_scheme and self.map.scheme then
1093                _uvl_validate_section(self, s)
1094            end
1095        end
1096        AbstractSection.parse_optionals(self, s)
1097
1098        if self.changed then
1099            self:push_events()
1100        end
1101    end
1102end
1103
1104
1105--[[
1106TypedSection - A (set of) configuration section(s) defined by the type
1107    addremove:  Defines whether the user can add/remove sections of this type
1108    anonymous:  Allow creating anonymous sections
1109    validate:   a validation function returning nil if the section is invalid
1110]]--
1111TypedSection = class(AbstractSection)
1112
1113function TypedSection.__init__(self, map, type, ...)
1114    AbstractSection.__init__(self, map, type, ...)
1115
1116    self.template  = "cbi/tsection"
1117    self.deps = {}
1118    self.anonymous = false
1119
1120    -- Use defaults from UVL
1121    if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1122        local vs = self.map:get_scheme(self.sectiontype)
1123        self.addremove = not vs.unique and not vs.required
1124        self.dynamic   = vs.dynamic
1125        self.anonymous = not vs.named
1126        self.title       = self.title or vs.title
1127        self.description = self.description or vs.descr
1128    end
1129end
1130
1131-- Return all matching UCI sections for this TypedSection
1132function TypedSection.cfgsections(self)
1133    local sections = {}
1134    self.map.uci:foreach(self.map.config, self.sectiontype,
1135        function (section)
1136            if self:checkscope(section[".name"]) then
1137                table.insert(sections, section[".name"])
1138            end
1139        end)
1140
1141    return sections
1142end
1143
1144-- Limits scope to sections that have certain option => value pairs
1145function TypedSection.depends(self, option, value)
1146    table.insert(self.deps, {option=option, value=value})
1147end
1148
1149function TypedSection.parse(self, novld)
1150    if self.addremove then
1151        -- Remove
1152        local crval = REMOVE_PREFIX .. self.config
1153        local name = self.map:formvaluetable(crval)
1154        for k,v in pairs(name) do
1155            if k:sub(-2) == ".x" then
1156                k = k:sub(1, #k - 2)
1157            end
1158            if self:cfgvalue(k) and self:checkscope(k) then
1159                self:remove(k)
1160            end
1161        end
1162    end
1163
1164    local co
1165    for i, k in ipairs(self:cfgsections()) do
1166        AbstractSection.parse_dynamic(self, k)
1167        if self.map:submitstate() then
1168            Node.parse(self, k, novld)
1169
1170            if not novld and not self.override_scheme and self.map.scheme then
1171                _uvl_validate_section(self, k)
1172            end
1173        end
1174        AbstractSection.parse_optionals(self, k)
1175    end
1176
1177    if self.addremove then
1178        -- Create
1179        local created
1180        local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1181        local name  = self.map:formvalue(crval)
1182        if self.anonymous then
1183            if name then
1184                created = self:create()
1185            end
1186        else
1187            if name then
1188                -- Ignore if it already exists
1189                if self:cfgvalue(name) then
1190                    name = nil;
1191                end
1192
1193                name = self:checkscope(name)
1194
1195                if not name then
1196                    self.err_invalid = true
1197                end
1198
1199                if name and #name > 0 then
1200                    created = self:create(name) and name
1201                    if not created then
1202                        self.invalid_cts = true
1203                    end
1204                end
1205            end
1206        end
1207
1208        if created then
1209            AbstractSection.parse_optionals(self, created)
1210        end
1211    end
1212
1213    if created or self.changed then
1214        self:push_events()
1215    end
1216end
1217
1218-- Verifies scope of sections
1219function TypedSection.checkscope(self, section)
1220    -- Check if we are not excluded
1221    if self.filter and not self:filter(section) then
1222        return nil
1223    end
1224
1225    -- Check if at least one dependency is met
1226    if #self.deps > 0 and self:cfgvalue(section) then
1227        local stat = false
1228
1229        for k, v in ipairs(self.deps) do
1230            if self:cfgvalue(section)[v.option] == v.value then
1231                stat = true
1232            end
1233        end
1234
1235        if not stat then
1236            return nil
1237        end
1238    end
1239
1240    return self:validate(section)
1241end
1242
1243
1244-- Dummy validate function
1245function TypedSection.validate(self, section)
1246    return section
1247end
1248
1249
1250--[[
1251AbstractValue - An abstract Value Type
1252    null:       Value can be empty
1253    valid:      A function returning the value if it is valid otherwise nil
1254    depends:    A table of option => value pairs of which one must be true
1255    default:    The default value
1256    size:       The size of the input fields
1257    rmempty:    Unset value if empty
1258    optional:   This value is optional (see AbstractSection.optionals)
1259]]--
1260AbstractValue = class(Node)
1261
1262function AbstractValue.__init__(self, map, section, option, ...)
1263    Node.__init__(self, ...)
1264    self.section = section
1265    self.option  = option
1266    self.map     = map
1267    self.config  = map.config
1268    self.tag_invalid = {}
1269    self.tag_missing = {}
1270    self.tag_reqerror = {}
1271    self.tag_error = {}
1272    self.deps = {}
1273    self.subdeps = {}
1274    --self.cast = "string"
1275
1276    self.track_missing = false
1277    self.rmempty   = true
1278    self.default   = nil
1279    self.size      = nil
1280    self.optional  = false
1281end
1282
1283function AbstractValue.prepare(self)
1284    -- Use defaults from UVL
1285    if not self.override_scheme
1286     and self.map:get_scheme(self.section.sectiontype, self.option) then
1287        local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1288        if self.cast == nil then
1289            self.cast = (vs.type == "list") and "list" or "string"
1290        end
1291        self.title       = self.title or vs.title
1292        self.description = self.description or vs.descr
1293        if self.default == nil then
1294            self.default = vs.default
1295        end
1296
1297        if vs.depends and not self.override_dependencies then
1298            for i, deps in ipairs(vs.depends) do
1299                deps = _uvl_strip_remote_dependencies(deps)
1300                if next(deps) then
1301                    self:depends(deps)
1302                end
1303            end
1304        end
1305    end
1306
1307    self.cast = self.cast or "string"
1308end
1309
1310-- Add a dependencie to another section field
1311function AbstractValue.depends(self, field, value)
1312    local deps
1313    if type(field) == "string" then
1314        deps = {}
1315        deps[field] = value
1316    else
1317        deps = field
1318    end
1319
1320    table.insert(self.deps, {deps=deps, add=""})
1321end
1322
1323-- Generates the unique CBID
1324function AbstractValue.cbid(self, section)
1325    return "cbid."..self.map.config.."."..section.."."..self.option
1326end
1327
1328-- Return whether this object should be created
1329function AbstractValue.formcreated(self, section)
1330    local key = "cbi.opt."..self.config.."."..section
1331    return (self.map:formvalue(key) == self.option)
1332end
1333
1334-- Returns the formvalue for this object
1335function AbstractValue.formvalue(self, section)
1336    return self.map:formvalue(self:cbid(section))
1337end
1338
1339function AbstractValue.additional(self, value)
1340    self.optional = value
1341end
1342
1343function AbstractValue.mandatory(self, value)
1344    self.rmempty = not value
1345end
1346
1347function AbstractValue.parse(self, section, novld)
1348    local fvalue = self:formvalue(section)
1349    local cvalue = self:cfgvalue(section)
1350
1351    -- If favlue and cvalue are both tables and have the same content
1352    -- make them identical
1353    if type(fvalue) == "table" and type(cvalue) == "table" then
1354        local equal = #fvalue == #cvalue
1355        if equal then
1356            for i=1, #fvalue do
1357                if cvalue[i] ~= fvalue[i] then
1358                    equal = false
1359                end
1360            end
1361        end
1362        if equal then
1363            fvalue = cvalue
1364        end
1365    end
1366
1367    if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1368        fvalue = self:transform(self:validate(fvalue, section))
1369        if not fvalue and not novld then
1370            if self.error then
1371                self.error[section] = "invalid"
1372            else
1373                self.error = { [section] = "invalid" }
1374            end
1375            if self.section.error then
1376                table.insert(self.section.error[section], "invalid")
1377            else
1378                self.section.error = {[section] = {"invalid"}}
1379            end 
1380            self.map.save = false
1381        end
1382        if fvalue and not (fvalue == cvalue) then
1383            if self:write(section, fvalue) then
1384                -- Push events
1385                self.section.changed = true
1386                --luci.util.append(self.map.events, self.events)
1387            end
1388        end
1389    else                            -- Unset the UCI or error
1390        if self.rmempty or self.optional then
1391            if self:remove(section) then
1392                -- Push events
1393                self.section.changed = true
1394                --luci.util.append(self.map.events, self.events)
1395            end
1396        elseif cvalue ~= fvalue and not novld then
1397            self:write(section, fvalue or "")
1398            if self.error then
1399                self.error[section] = "missing"
1400            else
1401                self.error = { [section] = "missing" }
1402            end
1403            self.map.save = false
1404        end
1405    end
1406end
1407
1408-- Render if this value exists or if it is mandatory
1409function AbstractValue.render(self, s, scope)
1410    if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1411        scope = scope or {}
1412        scope.section   = s
1413        scope.cbid      = self:cbid(s)
1414        scope.striptags = luci.util.striptags
1415        scope.pcdata    = luci.util.pcdata
1416
1417        scope.ifattr = function(cond,key,val)
1418            if cond then
1419                return string.format(
1420                    ' %s="%s"', tostring(key),
1421                    luci.util.pcdata(tostring( val
1422                     or scope[key]
1423                     or (type(self[key]) ~= "function" and self[key])
1424                     or "" ))
1425                )
1426            else
1427                return ''
1428            end
1429        end
1430
1431        scope.attr = function(...)
1432            return scope.ifattr( true, ... )
1433        end
1434
1435        Node.render(self, scope)
1436    end
1437end
1438
1439-- Return the UCI value of this object
1440function AbstractValue.cfgvalue(self, section)
1441    local value = self.map:get(section, self.option)
1442    if not value then
1443        return nil
1444    elseif not self.cast or self.cast == type(value) then
1445        return value
1446    elseif self.cast == "string" then
1447        if type(value) == "table" then
1448            return value[1]
1449        end
1450    elseif self.cast == "table" then
1451        return luci.util.split(value, "%s+", nil, true)
1452    end
1453end
1454
1455-- Validate the form value
1456function AbstractValue.validate(self, value)
1457    return value
1458end
1459
1460AbstractValue.transform = AbstractValue.validate
1461
1462
1463-- Write to UCI
1464function AbstractValue.write(self, section, value)
1465    return self.map:set(section, self.option, value)
1466end
1467
1468-- Remove from UCI
1469function AbstractValue.remove(self, section)
1470    return self.map:del(section, self.option)
1471end
1472
1473
1474
1475
1476--[[
1477Value - A one-line value
1478    maxlength:  The maximum length
1479]]--
1480Value = class(AbstractValue)
1481
1482function Value.__init__(self, ...)
1483    AbstractValue.__init__(self, ...)
1484    self.template  = "cbi/value"
1485    self.keylist = {}
1486    self.vallist = {}
1487end
1488
1489function Value.value(self, key, val)
1490    val = val or key
1491    table.insert(self.keylist, tostring(key))
1492    table.insert(self.vallist, tostring(val))
1493end
1494
1495
1496-- DummyValue - This does nothing except being there
1497DummyValue = class(AbstractValue)
1498
1499function DummyValue.__init__(self, ...)
1500    AbstractValue.__init__(self, ...)
1501    self.template = "cbi/dvalue"
1502    self.value = nil
1503end
1504
1505function DummyValue.cfgvalue(self, section)
1506    local value
1507    if self.value then
1508        if type(self.value) == "function" then
1509            value = self:value(section)
1510        else
1511            value = self.value
1512        end
1513    else
1514        value = AbstractValue.cfgvalue(self, section)
1515    end
1516    return value
1517end
1518
1519function DummyValue.parse(self)
1520
1521end
1522
1523
1524--[[
1525Flag - A flag being enabled or disabled
1526]]--
1527Flag = class(AbstractValue)
1528
1529function Flag.__init__(self, ...)
1530    AbstractValue.__init__(self, ...)
1531    self.template  = "cbi/fvalue"
1532
1533    self.enabled = "1"
1534    self.disabled = "0"
1535end
1536
1537-- A flag can only have two states: set or unset
1538function Flag.parse(self, section)
1539    local fvalue = self:formvalue(section)
1540
1541    if fvalue then
1542        fvalue = self.enabled
1543    else
1544        fvalue = self.disabled
1545    end
1546
1547    if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1548        if not(fvalue == self:cfgvalue(section)) then
1549            self:write(section, fvalue)
1550        end
1551    else
1552        self:remove(section)
1553    end
1554end
1555
1556
1557
1558--[[
1559ListValue - A one-line value predefined in a list
1560    widget: The widget that will be used (select, radio)
1561]]--
1562ListValue = class(AbstractValue)
1563
1564function ListValue.__init__(self, ...)
1565    AbstractValue.__init__(self, ...)
1566    self.template  = "cbi/lvalue"
1567
1568    self.keylist = {}
1569    self.vallist = {}
1570    self.size   = 1
1571    self.widget = "select"
1572end
1573
1574function ListValue.prepare(self, ...)
1575    AbstractValue.prepare(self, ...)
1576    if not self.override_scheme
1577     and self.map:get_scheme(self.section.sectiontype, self.option) then
1578        local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1579        if self.value and vs.valuelist and not self.override_values then
1580            for k, v in ipairs(vs.valuelist) do
1581                local deps = {}
1582                if not self.override_dependencies
1583                 and vs.enum_depends and vs.enum_depends[v.value] then
1584                    for i, dep in ipairs(vs.enum_depends[v.value]) do
1585                        table.insert(deps, _uvl_strip_remote_dependencies(dep))
1586                    end
1587                end
1588                self:value(v.value, v.title or v.value, unpack(deps))
1589            end
1590        end
1591    end
1592end
1593
1594function ListValue.value(self, key, val, ...)
1595    if luci.util.contains(self.keylist, key) then
1596        return
1597    end
1598
1599    val = val or key
1600    table.insert(self.keylist, tostring(key))
1601    table.insert(self.vallist, tostring(val))
1602
1603    for i, deps in ipairs({...}) do
1604        self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1605    end
1606end
1607
1608function ListValue.validate(self, val)
1609    if luci.util.contains(self.keylist, val) then
1610        return val
1611    else
1612        return nil
1613    end
1614end
1615
1616
1617
1618--[[
1619MultiValue - Multiple delimited values
1620    widget: The widget that will be used (select, checkbox)
1621    delimiter: The delimiter that will separate the values (default: " ")
1622]]--
1623MultiValue = class(AbstractValue)
1624
1625function MultiValue.__init__(self, ...)
1626    AbstractValue.__init__(self, ...)
1627    self.template = "cbi/mvalue"
1628
1629    self.keylist = {}
1630    self.vallist = {}
1631
1632    self.widget = "checkbox"
1633    self.delimiter = " "
1634end
1635
1636function MultiValue.render(self, ...)
1637    if self.widget == "select" and not self.size then
1638        self.size = #self.vallist
1639    end
1640
1641    AbstractValue.render(self, ...)
1642end
1643
1644function MultiValue.value(self, key, val)
1645    if luci.util.contains(self.keylist, key) then
1646        return
1647    end
1648
1649    val = val or key
1650    table.insert(self.keylist, tostring(key))
1651    table.insert(self.vallist, tostring(val))
1652end
1653
1654function MultiValue.valuelist(self, section)
1655    local val = self:cfgvalue(section)
1656
1657    if not(type(val) == "string") then
1658        return {}
1659    end
1660
1661    return luci.util.split(val, self.delimiter)
1662end
1663
1664function MultiValue.validate(self, val)
1665    val = (type(val) == "table") and val or {val}
1666
1667    local result
1668
1669    for i, value in ipairs(val) do
1670        if luci.util.contains(self.keylist, value) then
1671            result = result and (result .. self.delimiter .. value) or value
1672        end
1673    end
1674
1675    return result
1676end
1677
1678
1679StaticList = class(MultiValue)
1680
1681function StaticList.__init__(self, ...)
1682    MultiValue.__init__(self, ...)
1683    self.cast = "table"
1684    self.valuelist = self.cfgvalue
1685
1686    if not self.override_scheme
1687     and self.map:get_scheme(self.section.sectiontype, self.option) then
1688        local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1689        if self.value and vs.values and not self.override_values then
1690            for k, v in pairs(vs.values) do
1691                self:value(k, v)
1692            end
1693        end
1694    end
1695end
1696
1697function StaticList.validate(self, value)
1698    value = (type(value) == "table") and value or {value}
1699
1700    local valid = {}
1701    for i, v in ipairs(value) do
1702        if luci.util.contains(self.keylist, v) then
1703            table.insert(valid, v)
1704        end
1705    end
1706    return valid
1707end
1708
1709
1710DynamicList = class(AbstractValue)
1711
1712function DynamicList.__init__(self, ...)
1713    AbstractValue.__init__(self, ...)
1714    self.template  = "cbi/dynlist"
1715    self.cast = "table"
1716    self.keylist = {}
1717    self.vallist = {}
1718end
1719
1720function DynamicList.value(self, key, val)
1721    val = val or key
1722    table.insert(self.keylist, tostring(key))
1723    table.insert(self.vallist, tostring(val))
1724end
1725
1726function DynamicList.write(self, ...)
1727    self.map.proceed = true
1728    return AbstractValue.write(self, ...)
1729end
1730
1731function DynamicList.formvalue(self, section)
1732    local value = AbstractValue.formvalue(self, section)
1733    value = (type(value) == "table") and value or {value}
1734
1735    local valid = {}
1736    for i, v in ipairs(value) do
1737        if v and #v > 0
1738         and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1739         and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1740            table.insert(valid, v)
1741        end
1742    end
1743
1744    return valid
1745end
1746
1747
1748--[[
1749TextValue - A multi-line value
1750    rows:   Rows
1751]]--
1752TextValue = class(AbstractValue)
1753
1754function TextValue.__init__(self, ...)
1755    AbstractValue.__init__(self, ...)
1756    self.template  = "cbi/tvalue"
1757end
1758
1759--[[
1760Button
1761]]--
1762Button = class(AbstractValue)
1763
1764function Button.__init__(self, ...)
1765    AbstractValue.__init__(self, ...)
1766    self.template  = "cbi/button"
1767    self.inputstyle = nil
1768    self.rmempty = true
1769end
1770
1771
1772FileUpload = class(AbstractValue)
1773
1774function FileUpload.__init__(self, ...)
1775    AbstractValue.__init__(self, ...)
1776    self.template = "cbi/upload"
1777    if not self.map.upload_fields then
1778        self.map.upload_fields = { self }
1779    else
1780        self.map.upload_fields[#self.map.upload_fields+1] = self
1781    end
1782end
1783
1784function FileUpload.formcreated(self, section)
1785    return AbstractValue.formcreated(self, section) or
1786        self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1787        self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1788end
1789
1790function FileUpload.cfgvalue(self, section)
1791    local val = AbstractValue.cfgvalue(self, section)
1792    if val and fs.access(val) then
1793        return val
1794    end
1795    return nil
1796end
1797
1798function FileUpload.formvalue(self, section)
1799    local val = AbstractValue.formvalue(self, section)
1800    if val then
1801        if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1802           not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1803        then
1804            return val
1805        end
1806        fs.unlink(val)
1807        self.value = nil
1808    end
1809    return nil
1810end
1811
1812function FileUpload.remove(self, section)
1813    local val = AbstractValue.formvalue(self, section)
1814    if val and fs.access(val) then fs.unlink(val) end
1815    return AbstractValue.remove(self, section)
1816end
1817
1818
1819FileBrowser = class(AbstractValue)
1820
1821function FileBrowser.__init__(self, ...)
1822    AbstractValue.__init__(self, ...)
1823    self.template = "cbi/browser"
1824end
Note: See TracBrowser for help on using the browser.