modules/admin-mini: Added Wifi configuration
[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
308         
309         if section then
310                 stat = self.map:set(section, nil, self.sectiontype)
311         else
312                 section = self.map:add(self.sectiontype)
313                 stat = section
314         end
315
316         if stat then
317                 for k,v in pairs(self.children) do
318                         if v.default then
319                                 self.map:set(section, v.option, v.default)
320                         end
321                 end
322
323                 for k,v in pairs(self.defaults) do
324                         self.map:set(section, k, v)
325                 end
326         end
327
328         return stat
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) then
360                                 self:create(s)
361                                 return
362                         end
363                 end
364         end
365
366         if active then
367                 AbstractSection.parse_dynamic(self, s)
368                 if luci.http.formvalue("cbi.submit") then
369                         Node.parse(self, s)
370                 end
371                 AbstractSection.parse_optionals(self, s)
372         end
373 end
374
375
376 --[[
377 TypedSection - A (set of) configuration section(s) defined by the type
378         addremove:      Defines whether the user can add/remove sections of this type
379         anonymous:  Allow creating anonymous sections
380         validate:       a validation function returning nil if the section is invalid
381 ]]--
382 TypedSection = class(AbstractSection)
383
384 function TypedSection.__init__(self, map, type, ...)
385         AbstractSection.__init__(self, map, type, ...)
386         Node._i18n(self, map.config, type, nil, ...)
387
388         self.template  = "cbi/tsection"
389         self.deps = {}
390         self.excludes = {}
391
392         self.anonymous = false
393 end
394
395 -- Return all matching UCI sections for this TypedSection
396 function TypedSection.cfgsections(self)
397         local sections = {}
398         uci.foreach(self.map.config, self.sectiontype,
399                 function (section)
400                         if self:checkscope(section[".name"]) then
401                                 table.insert(sections, section[".name"])
402                         end
403                 end)
404
405         return sections
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
646 --[[
647 Flag - A flag being enabled or disabled
648 ]]--
649 Flag = class(AbstractValue)
650
651 function Flag.__init__(self, ...)
652         AbstractValue.__init__(self, ...)
653         self.template  = "cbi/fvalue"
654
655         self.enabled = "1"
656         self.disabled = "0"
657 end
658
659 -- A flag can only have two states: set or unset
660 function Flag.parse(self, section)
661         local fvalue = self:formvalue(section)
662
663         if fvalue then
664                 fvalue = self.enabled
665         else
666                 fvalue = self.disabled
667         end
668
669         if fvalue == self.enabled or (not self.optional and not self.rmempty) then
670                 if not(fvalue == self:cfgvalue(section)) then
671                         self:write(section, fvalue)
672                 end
673         else
674                 self:remove(section)
675         end
676 end
677
678
679
680 --[[
681 ListValue - A one-line value predefined in a list
682         widget: The widget that will be used (select, radio)
683 ]]--
684 ListValue = class(AbstractValue)
685
686 function ListValue.__init__(self, ...)
687         AbstractValue.__init__(self, ...)
688         self.template  = "cbi/lvalue"
689         self.keylist = {}
690         self.vallist = {}
691
692         self.size   = 1
693         self.widget = "select"
694 end
695
696 function ListValue.value(self, key, val)
697         val = val or key
698         table.insert(self.keylist, tostring(key))
699         table.insert(self.vallist, tostring(val))
700 end
701
702 function ListValue.validate(self, val)
703         if luci.util.contains(self.keylist, val) then
704                 return val
705         else
706                 return nil
707         end
708 end
709
710
711
712 --[[
713 MultiValue - Multiple delimited values
714         widget: The widget that will be used (select, checkbox)
715         delimiter: The delimiter that will separate the values (default: " ")
716 ]]--
717 MultiValue = class(AbstractValue)
718
719 function MultiValue.__init__(self, ...)
720         AbstractValue.__init__(self, ...)
721         self.template = "cbi/mvalue"
722         self.keylist = {}
723         self.vallist = {}
724
725         self.widget = "checkbox"
726         self.delimiter = " "
727 end
728
729 function MultiValue.render(self, ...)
730         if self.widget == "select" and not self.size then
731                 self.size = #self.vallist
732         end
733
734         AbstractValue.render(self, ...)
735 end
736
737 function MultiValue.value(self, key, val)
738         val = val or key
739         table.insert(self.keylist, tostring(key))
740         table.insert(self.vallist, tostring(val))
741 end
742
743 function MultiValue.valuelist(self, section)
744         local val = self:cfgvalue(section)
745
746         if not(type(val) == "string") then
747                 return {}
748         end
749
750         return luci.util.split(val, self.delimiter)
751 end
752
753 function MultiValue.validate(self, val)
754         val = (type(val) == "table") and val or {val}
755
756         local result
757
758         for i, value in ipairs(val) do
759                 if luci.util.contains(self.keylist, value) then
760                         result = result and (result .. self.delimiter .. value) or value
761                 end
762         end
763
764         return result
765 end