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