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

Revision 6762, 37.9 KB (checked in by jow, 2 years ago)

libs/web: implement sortable rows for uci reordering

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