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