0ec2ff815de7095fe94c56fefc5ac6ff9498d62c
[project/luci.git] / libs / cbi / luasrc / cbi.lua
1 --[[
2 LuCI - Configuration Bind Interface
3
4 Description:
5 Offers an interface for binding configuration values to certain
6 data types. Supports value and range validation and basic dependencies.
7
8 FileId:
9 $Id$
10
11 License:
12 Copyright 2008 Steven Barth <steven@midlink.org>
13
14 Licensed under the Apache License, Version 2.0 (the "License");
15 you may not use this file except in compliance with the License.
16 You may obtain a copy of the License at
17
18         http://www.apache.org/licenses/LICENSE-2.0
19
20 Unless required by applicable law or agreed to in writing, software
21 distributed under the License is distributed on an "AS IS" BASIS,
22 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 See the License for the specific language governing permissions and
24 limitations under the License.
25
26 ]]--
27 module("luci.cbi", package.seeall)
28
29 require("luci.template")
30 require("luci.util")
31 require("luci.http")
32 require("luci.model.uci")
33
34 local uci        = luci.model.uci
35 local class      = luci.util.class
36 local instanceof = luci.util.instanceof
37
38 FORM_NODATA  =  0
39 FORM_VALID   =  1
40 FORM_INVALID = -1
41
42 -- Loads a CBI map from given file, creating an environment and returns it
43 function load(cbimap, ...)
44         require("luci.fs")
45         require("luci.i18n")
46         require("luci.config")
47         require("luci.util")
48
49         local cbidir = luci.util.libpath() .. "/model/cbi/"
50         local func, err = loadfile(cbidir..cbimap..".lua")
51
52         if not func then
53                 return nil
54         end
55
56         luci.i18n.loadc("cbi")
57
58         luci.util.resfenv(func)
59         luci.util.updfenv(func, luci.cbi)
60         luci.util.extfenv(func, "translate", luci.i18n.translate)
61         luci.util.extfenv(func, "translatef", luci.i18n.translatef)
62         luci.util.extfenv(func, "arg", {...})
63
64         local maps = {func()}
65
66         for i, map in ipairs(maps) do
67                 if not instanceof(map, Node) then
68                         error("CBI map returns no valid map object!")
69                         return nil
70                 end
71         end
72
73         return maps
74 end
75
76 -- Node pseudo abstract class
77 Node = class()
78
79 function Node.__init__(self, title, description)
80         self.children = {}
81         self.title = title or ""
82         self.description = description or ""
83         self.template = "cbi/node"
84 end
85
86 -- i18n helper
87 function Node._i18n(self, config, section, option, title, description)
88
89         -- i18n loaded?
90         if type(luci.i18n) == "table" then
91
92                 local key = config:gsub("[^%w]+", "")
93
94                 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
95                 if option  then key = key .. "_" .. option:lower():gsub("[^%w]+", "")  end
96
97                 self.title = title or luci.i18n.translate( key, option or section or config )
98                 self.description = description or luci.i18n.translate( key .. "_desc", "" )
99         end
100 end
101
102 -- Append child nodes
103 function Node.append(self, obj)
104         table.insert(self.children, obj)
105 end
106
107 -- Parse this node and its children
108 function Node.parse(self, ...)
109         for k, child in ipairs(self.children) do
110                 child:parse(...)
111         end
112 end
113
114 -- Render this node
115 function Node.render(self, scope)
116         scope = scope or {}
117         scope.self = self
118
119         luci.template.render(self.template, scope)
120 end
121
122 -- Render the children
123 function Node.render_children(self, ...)
124         for k, node in ipairs(self.children) do
125                 node:render(...)
126         end
127 end
128
129
130 --[[
131 A simple template element
132 ]]--
133 Template = class(Node)
134
135 function Template.__init__(self, template)
136         Node.__init__(self)
137         self.template = template
138 end
139
140
141 --[[
142 Map - A map describing a configuration file
143 ]]--
144 Map = class(Node)
145
146 function Map.__init__(self, config, ...)
147         Node.__init__(self, ...)
148         Node._i18n(self, config, nil, nil, ...)
149
150         self.config = config
151         self.parsechain = {self.config}
152         self.template = "cbi/map"
153         if not uci.load(self.config) then
154                 error("Unable to read UCI data: " .. self.config)
155         end
156 end
157
158
159 -- Chain foreign config
160 function Map.chain(self, config)
161         table.insert(self.parsechain, config)
162 end
163
164 -- Use optimized UCI writing
165 function Map.parse(self, ...)
166         Node.parse(self, ...)
167         for i, config in ipairs(self.parsechain) do
168                 uci.save(config)
169         end
170         if luci.http.formvalue("cbi.apply") then
171                 for i, config in ipairs(self.parsechain) do
172                         uci.commit(config)
173                         if luci.config.uci_oncommit and luci.config.uci_oncommit[config] then
174                                 luci.util.exec(luci.config.uci_oncommit[config])
175                         end
176
177                         -- Refresh data because commit changes section names
178                         uci.unload(config)
179                         uci.load(config)
180                 end
181
182                 -- Reparse sections
183                 Node.parse(self, ...)
184
185         end
186         for i, config in ipairs(self.parsechain) do
187                 uci.unload(config)
188         end
189 end
190
191 -- Creates a child section
192 function Map.section(self, class, ...)
193         if instanceof(class, AbstractSection) then
194                 local obj  = class(self, ...)
195                 self:append(obj)
196                 return obj
197         else
198                 error("class must be a descendent of AbstractSection")
199         end
200 end
201
202 -- UCI add
203 function Map.add(self, sectiontype)
204         return uci.add(self.config, sectiontype)
205 end
206
207 -- UCI set
208 function Map.set(self, section, option, value)
209         if option then
210                 return uci.set(self.config, section, option, value)
211         else
212                 return uci.set(self.config, section, value)
213         end
214 end
215
216 -- UCI del
217 function Map.del(self, section, option)
218         if option then
219                 return uci.delete(self.config, section, option)
220         else
221                 return uci.delete(self.config, section)
222         end
223 end
224
225 -- UCI get
226 function Map.get(self, section, option)
227         if not section then
228                 return uci.get_all(self.config)
229         elseif option then
230                 return uci.get(self.config, section, option)
231         else
232                 return uci.get_all(self.config, section)
233         end
234 end
235
236
237 --[[
238 SimpleForm - A Simple non-UCI form
239 ]]--
240 SimpleForm = class(Node)
241
242 function SimpleForm.__init__(self, config, title, description, data)
243         Node.__init__(self, title, description)
244         self.config = config
245         self.data = data or {}
246         self.template = "cbi/simpleform"
247         self.dorender = true
248 end
249
250 function SimpleForm.parse(self, ...)
251         Node.parse(self, 1, ...)
252                 
253         local valid = true
254         for i, v in ipairs(self.children) do
255                 valid = valid and not v.tag_missing[1] and not v.tag_invalid[1]
256         end
257         
258         local state = 
259                 not luci.http.formvalue("cbi.submit") and 0
260                 or valid and 1
261                 or -1
262
263         self.dorender = self:handle(state)
264 end
265
266 function SimpleForm.render(self, ...)
267         if self.dorender then
268                 Node.render(self, ...)
269         end
270 end
271
272 -- Creates a child section
273 function SimpleForm.field(self, class, ...)
274         if instanceof(class, AbstractValue) then
275                 local obj  = class(self, ...)
276                 self:append(obj)
277                 return obj
278         else
279                 error("class must be a descendent of AbstractValue")
280         end
281 end
282
283 function SimpleForm.set(self, section, option, value)
284         self.data[option] = value
285 end
286
287
288 function SimpleForm.del(self, section, option)
289         self.data[option] = nil
290 end
291
292
293 function SimpleForm.get(self, section, option)
294         return self.data[option]
295 end
296
297
298
299 --[[
300 AbstractSection
301 ]]--
302 AbstractSection = class(Node)
303
304 function AbstractSection.__init__(self, map, sectiontype, ...)
305         Node.__init__(self, ...)
306         self.sectiontype = sectiontype
307         self.map = map
308         self.config = map.config
309         self.optionals = {}
310         self.defaults = {}
311
312         self.optional = true
313         self.addremove = false
314         self.dynamic = false
315 end
316
317 -- Appends a new option
318 function AbstractSection.option(self, class, option, ...)
319         if instanceof(class, AbstractValue) then
320                 local obj  = class(self.map, option, ...)
321
322                 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
323
324                 self:append(obj)
325                 return obj
326         else
327                 error("class must be a descendent of AbstractValue")
328         end
329 end
330
331 -- Parse optional options
332 function AbstractSection.parse_optionals(self, section)
333         if not self.optional then
334                 return
335         end
336
337         self.optionals[section] = {}
338
339         local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
340         for k,v in ipairs(self.children) do
341                 if v.optional and not v:cfgvalue(section) then
342                         if field == v.option then
343                                 field = nil
344                         else
345                                 table.insert(self.optionals[section], v)
346                         end
347                 end
348         end
349
350         if field and #field > 0 and self.dynamic then
351                 self:add_dynamic(field)
352         end
353 end
354
355 -- Add a dynamic option
356 function AbstractSection.add_dynamic(self, field, optional)
357         local o = self:option(Value, field, field)
358         o.optional = optional
359 end
360
361 -- Parse all dynamic options
362 function AbstractSection.parse_dynamic(self, section)
363         if not self.dynamic then
364                 return
365         end
366
367         local arr  = luci.util.clone(self:cfgvalue(section))
368         local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
369         for k, v in pairs(form) do
370                 arr[k] = v
371         end
372
373         for key,val in pairs(arr) do
374                 local create = true
375
376                 for i,c in ipairs(self.children) do
377                         if c.option == key then
378                                 create = false
379                         end
380                 end
381
382                 if create and key:sub(1, 1) ~= "." then
383                         self:add_dynamic(key, true)
384                 end
385         end
386 end
387
388 -- Returns the section's UCI table
389 function AbstractSection.cfgvalue(self, section)
390         return self.map:get(section)
391 end
392
393 -- Removes the section
394 function AbstractSection.remove(self, section)
395         return self.map:del(section)
396 end
397
398 -- Creates the section
399 function AbstractSection.create(self, section)
400         local stat
401         
402         if section then
403                 stat = self.map:set(section, nil, self.sectiontype)
404         else
405                 section = self.map:add(self.sectiontype)
406                 stat = section
407         end
408
409         if stat then
410                 for k,v in pairs(self.children) do
411                         if v.default then
412                                 self.map:set(section, v.option, v.default)
413                         end
414                 end
415
416                 for k,v in pairs(self.defaults) do
417                         self.map:set(section, k, v)
418                 end
419         end
420
421         return stat
422 end
423
424
425
426 --[[
427 NamedSection - A fixed configuration section defined by its name
428 ]]--
429 NamedSection = class(AbstractSection)
430
431 function NamedSection.__init__(self, map, section, type, ...)
432         AbstractSection.__init__(self, map, type, ...)
433         Node._i18n(self, map.config, section, nil, ...)
434
435         self.template = "cbi/nsection"
436         self.section = section
437         self.addremove = false
438 end
439
440 function NamedSection.parse(self)
441         local s = self.section
442         local active = self:cfgvalue(s)
443
444
445         if self.addremove then
446                 local path = self.config.."."..s
447                 if active then -- Remove the section
448                         if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
449                                 return
450                         end
451                 else           -- Create and apply default values
452                         if luci.http.formvalue("cbi.cns."..path) then
453                                 self:create(s)
454                                 return
455                         end
456                 end
457         end
458
459         if active then
460                 AbstractSection.parse_dynamic(self, s)
461                 if luci.http.formvalue("cbi.submit") then
462                         Node.parse(self, s)
463                 end
464                 AbstractSection.parse_optionals(self, s)
465         end
466 end
467
468
469 --[[
470 TypedSection - A (set of) configuration section(s) defined by the type
471         addremove:      Defines whether the user can add/remove sections of this type
472         anonymous:  Allow creating anonymous sections
473         validate:       a validation function returning nil if the section is invalid
474 ]]--
475 TypedSection = class(AbstractSection)
476
477 function TypedSection.__init__(self, map, type, ...)
478         AbstractSection.__init__(self, map, type, ...)
479         Node._i18n(self, map.config, type, nil, ...)
480
481         self.template  = "cbi/tsection"
482         self.deps = {}
483
484         self.anonymous = false
485 end
486
487 -- Return all matching UCI sections for this TypedSection
488 function TypedSection.cfgsections(self)
489         local sections = {}
490         uci.foreach(self.map.config, self.sectiontype,
491                 function (section)
492                         if self:checkscope(section[".name"]) then
493                                 table.insert(sections, section[".name"])
494                         end
495                 end)
496
497         return sections
498 end
499
500 -- Limits scope to sections that have certain option => value pairs
501 function TypedSection.depends(self, option, value)
502         table.insert(self.deps, {option=option, value=value})
503 end
504
505 function TypedSection.parse(self)
506         if self.addremove then
507                 -- Create
508                 local crval = "cbi.cts." .. self.config .. "." .. self.sectiontype
509                 local name  = luci.http.formvalue(crval)
510                 if self.anonymous then
511                         if name then
512                                 self:create()
513                         end
514                 else
515                         if name then
516                                 -- Ignore if it already exists
517                                 if self:cfgvalue(name) then
518                                         name = nil;
519                                 end
520
521                                 name = self:checkscope(name)
522
523                                 if not name then
524                                         self.err_invalid = true
525                                 end
526
527                                 if name and name:len() > 0 then
528                                         self:create(name)
529                                 end
530                         end
531                 end
532
533                 -- Remove
534                 crval = "cbi.rts." .. self.config
535                 name = luci.http.formvaluetable(crval)
536                 for k,v in pairs(name) do
537                         if self:cfgvalue(k) and self:checkscope(k) then
538                                 self:remove(k)
539                         end
540                 end
541         end
542
543         for i, k in ipairs(self:cfgsections()) do
544                 AbstractSection.parse_dynamic(self, k)
545                 if luci.http.formvalue("cbi.submit") then
546                         Node.parse(self, k)
547                 end
548                 AbstractSection.parse_optionals(self, k)
549         end
550 end
551
552 -- Verifies scope of sections
553 function TypedSection.checkscope(self, section)
554         -- Check if we are not excluded
555         if self.filter and not self:filter(section) then
556                 return nil
557         end
558
559         -- Check if at least one dependency is met
560         if #self.deps > 0 and self:cfgvalue(section) then
561                 local stat = false
562
563                 for k, v in ipairs(self.deps) do
564                         if self:cfgvalue(section)[v.option] == v.value then
565                                 stat = true
566                         end
567                 end
568
569                 if not stat then
570                         return nil
571                 end
572         end
573
574         return self:validate(section)
575 end
576
577
578 -- Dummy validate function
579 function TypedSection.validate(self, section)
580         return section
581 end
582
583
584 --[[
585 AbstractValue - An abstract Value Type
586         null:           Value can be empty
587         valid:          A function returning the value if it is valid otherwise nil
588         depends:        A table of option => value pairs of which one must be true
589         default:        The default value
590         size:           The size of the input fields
591         rmempty:        Unset value if empty
592         optional:       This value is optional (see AbstractSection.optionals)
593 ]]--
594 AbstractValue = class(Node)
595
596 function AbstractValue.__init__(self, map, option, ...)
597         Node.__init__(self, ...)
598         self.option = option
599         self.map    = map
600         self.config = map.config
601         self.tag_invalid = {}
602         self.tag_missing = {}
603         self.deps = {}
604
605         self.rmempty   = false
606         self.default   = nil
607         self.size      = nil
608         self.optional  = false
609 end
610
611 -- Add a dependencie to another section field
612 function AbstractValue.depends(self, field, value)
613         table.insert(self.deps, {field=field, value=value})
614 end
615
616 -- Return whether this object should be created
617 function AbstractValue.formcreated(self, section)
618         local key = "cbi.opt."..self.config.."."..section
619         return (luci.http.formvalue(key) == self.option)
620 end
621
622 -- Returns the formvalue for this object
623 function AbstractValue.formvalue(self, section)
624         local key = "cbid."..self.map.config.."."..section.."."..self.option
625         return luci.http.formvalue(key)
626 end
627
628 function AbstractValue.additional(self, value)
629         self.optional = value
630 end
631
632 function AbstractValue.mandatory(self, value)
633         self.rmempty = not value
634 end
635
636 function AbstractValue.parse(self, section)
637         local fvalue = self:formvalue(section)
638         local cvalue = self:cfgvalue(section)
639
640         if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
641                 fvalue = self:transform(self:validate(fvalue))
642                 if not fvalue then
643                         self.tag_invalid[section] = true
644                 end
645                 if fvalue and not (fvalue == self:cfgvalue(section)) then
646                         self:write(section, fvalue)
647                 end
648         else                                                    -- Unset the UCI or error
649                 if self.rmempty or self.optional then
650                         self:remove(section)
651                 elseif not fvalue or fvalue ~= cvalue then
652                         self.tag_missing[section] = true
653                 end
654         end
655 end
656
657 -- Render if this value exists or if it is mandatory
658 function AbstractValue.render(self, s, scope)
659         if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
660                 scope = scope or {}
661                 scope.section = s
662                 scope.cbid    = "cbid." .. self.config ..
663                                 "."     .. s           ..
664                                                 "."     .. self.option
665
666                 scope.ifattr = function(cond,key,val)
667                         if cond then
668                                 return string.format(
669                                         ' %s="%s"', tostring(key),
670                                         tostring( val
671                                          or scope[key]
672                                          or (type(self[key]) ~= "function" and self[key])
673                                          or "" )
674                                 )
675                         else
676                                 return ''
677                         end
678                 end
679
680                 scope.attr = function(...)
681                         return scope.ifattr( true, ... )
682                 end
683
684                 Node.render(self, scope)
685         end
686 end
687
688 -- Return the UCI value of this object
689 function AbstractValue.cfgvalue(self, section)
690         return self.map:get(section, self.option)
691 end
692
693 -- Validate the form value
694 function AbstractValue.validate(self, value)
695         return value
696 end
697
698 AbstractValue.transform = AbstractValue.validate
699
700
701 -- Write to UCI
702 function AbstractValue.write(self, section, value)
703         return self.map:set(section, self.option, value)
704 end
705
706 -- Remove from UCI
707 function AbstractValue.remove(self, section)
708         return self.map:del(section, self.option)
709 end
710
711
712
713
714 --[[
715 Value - A one-line value
716         maxlength:      The maximum length
717 ]]--
718 Value = class(AbstractValue)
719
720 function Value.__init__(self, ...)
721         AbstractValue.__init__(self, ...)
722         self.template  = "cbi/value"
723         self.keylist = {}
724         self.vallist = {}
725 end
726
727 function Value.value(self, key, val)
728         val = val or key
729         table.insert(self.keylist, tostring(key))
730         table.insert(self.vallist, tostring(val))
731 end
732
733
734 -- DummyValue - This does nothing except being there
735 DummyValue = class(AbstractValue)
736
737 function DummyValue.__init__(self, map, ...)
738         AbstractValue.__init__(self, map, ...)
739         self.template = "cbi/dvalue"
740         self.value = nil
741 end
742
743 function DummyValue.parse(self)
744
745 end
746
747
748 --[[
749 Flag - A flag being enabled or disabled
750 ]]--
751 Flag = class(AbstractValue)
752
753 function Flag.__init__(self, ...)
754         AbstractValue.__init__(self, ...)
755         self.template  = "cbi/fvalue"
756
757         self.enabled = "1"
758         self.disabled = "0"
759 end
760
761 -- A flag can only have two states: set or unset
762 function Flag.parse(self, section)
763         local fvalue = self:formvalue(section)
764
765         if fvalue then
766                 fvalue = self.enabled
767         else
768                 fvalue = self.disabled
769         end
770
771         if fvalue == self.enabled or (not self.optional and not self.rmempty) then
772                 if not(fvalue == self:cfgvalue(section)) then
773                         self:write(section, fvalue)
774                 end
775         else
776                 self:remove(section)
777         end
778 end
779
780
781
782 --[[
783 ListValue - A one-line value predefined in a list
784         widget: The widget that will be used (select, radio)
785 ]]--
786 ListValue = class(AbstractValue)
787
788 function ListValue.__init__(self, ...)
789         AbstractValue.__init__(self, ...)
790         self.template  = "cbi/lvalue"
791         self.keylist = {}
792         self.vallist = {}
793
794         self.size   = 1
795         self.widget = "select"
796 end
797
798 function ListValue.value(self, key, val)
799         val = val or key
800         table.insert(self.keylist, tostring(key))
801         table.insert(self.vallist, tostring(val))
802 end
803
804 function ListValue.validate(self, val)
805         if luci.util.contains(self.keylist, val) then
806                 return val
807         else
808                 return nil
809         end
810 end
811
812
813
814 --[[
815 MultiValue - Multiple delimited values
816         widget: The widget that will be used (select, checkbox)
817         delimiter: The delimiter that will separate the values (default: " ")
818 ]]--
819 MultiValue = class(AbstractValue)
820
821 function MultiValue.__init__(self, ...)
822         AbstractValue.__init__(self, ...)
823         self.template = "cbi/mvalue"
824         self.keylist = {}
825         self.vallist = {}
826
827         self.widget = "checkbox"
828         self.delimiter = " "
829 end
830
831 function MultiValue.render(self, ...)
832         if self.widget == "select" and not self.size then
833                 self.size = #self.vallist
834         end
835
836         AbstractValue.render(self, ...)
837 end
838
839 function MultiValue.value(self, key, val)
840         val = val or key
841         table.insert(self.keylist, tostring(key))
842         table.insert(self.vallist, tostring(val))
843 end
844
845 function MultiValue.valuelist(self, section)
846         local val = self:cfgvalue(section)
847
848         if not(type(val) == "string") then
849                 return {}
850         end
851
852         return luci.util.split(val, self.delimiter)
853 end
854
855 function MultiValue.validate(self, val)
856         val = (type(val) == "table") and val or {val}
857
858         local result
859
860         for i, value in ipairs(val) do
861                 if luci.util.contains(self.keylist, value) then
862                         result = result and (result .. self.delimiter .. value) or value
863                 end
864         end
865
866         return result
867 end