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