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