root/luci/trunk/libs/web/luasrc/cbi.lua @ 6608

Revision 6608, 37.5 KB (checked in by jow, 3 years ago)

libs/web: handle empty tables in Map.set()

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