31fa8bffb4353d08d9907f00b5b6d9a6438f49d2
[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, section, ...)
160         if instanceof(class, AbstractSection) then
161                 local obj  = class(self, section, ...)
162
163                 Node._i18n(obj, self.config, section, nil, ...)
164
165                 self:append(obj)
166                 return obj
167         else
168                 error("class must be a descendent of AbstractSection")
169         end
170 end
171
172 -- UCI add
173 function Map.add(self, sectiontype)
174         local name = self.uci:t_add(self.config, sectiontype)
175         if name then
176                 self.ucidata[name] = {}
177                 self.ucidata[name][".type"] = sectiontype
178                 table.insert(self.uciorder, name)
179         end
180         return name
181 end
182
183 -- UCI set
184 function Map.set(self, section, option, value)
185         local stat = self.uci:t_set(self.config, section, option, value)
186         if stat then
187                 local val = self.uci:t_get(self.config, section, option)
188                 if option then
189                         self.ucidata[section][option] = val
190                 else
191                         if not self.ucidata[section] then
192                                 self.ucidata[section] = {}
193                         end
194                         self.ucidata[section][".type"] = val
195                         table.insert(self.uciorder, section)
196                 end
197         end
198         return stat
199 end
200
201 -- UCI del
202 function Map.del(self, section, option)
203         local stat = self.uci:t_del(self.config, section, option)
204         if stat then
205                 if option then
206                         self.ucidata[section][option] = nil
207                 else
208                         self.ucidata[section] = nil
209                         for i, k in ipairs(self.uciorder) do
210                                 if section == k then
211                                         table.remove(self.uciorder, i)
212                                 end
213                         end
214                 end
215         end
216         return stat
217 end
218
219 -- UCI get (cached)
220 function Map.get(self, section, option)
221         if not section then
222                 return self.ucidata, self.uciorder
223         elseif option and self.ucidata[section] then
224                 return self.ucidata[section][option]
225         else
226                 return self.ucidata[section]
227         end
228 end
229
230
231 --[[
232 AbstractSection
233 ]]--
234 AbstractSection = class(Node)
235
236 function AbstractSection.__init__(self, map, sectiontype, ...)
237         Node.__init__(self, ...)
238         self.sectiontype = sectiontype
239         self.map = map
240         self.config = map.config
241         self.optionals = {}
242
243         self.optional = true
244         self.addremove = false
245         self.dynamic = false
246 end
247
248 -- Appends a new option
249 function AbstractSection.option(self, class, option, ...)
250         if instanceof(class, AbstractValue) then
251                 local obj  = class(self.map, option, ...)
252
253                 Node._i18n(obj, self.config, self.section, option, ...)
254
255                 self:append(obj)
256                 return obj
257         else
258                 error("class must be a descendent of AbstractValue")
259         end
260 end
261
262 -- Parse optional options
263 function AbstractSection.parse_optionals(self, section)
264         if not self.optional then
265                 return
266         end
267
268         self.optionals[section] = {}
269
270         local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
271         for k,v in ipairs(self.children) do
272                 if v.optional and not v:cfgvalue(section) then
273                         if field == v.option then
274                                 field = nil
275                         else
276                                 table.insert(self.optionals[section], v)
277                         end
278                 end
279         end
280
281         if field and #field > 0 and self.dynamic then
282                 self:add_dynamic(field)
283         end
284 end
285
286 -- Add a dynamic option
287 function AbstractSection.add_dynamic(self, field, optional)
288         local o = self:option(Value, field, field)
289         o.optional = optional
290 end
291
292 -- Parse all dynamic options
293 function AbstractSection.parse_dynamic(self, section)
294         if not self.dynamic then
295                 return
296         end
297
298         local arr  = luci.util.clone(self:cfgvalue(section))
299         local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
300         for k, v in pairs(form) do
301                 arr[k] = v
302         end
303
304         for key,val in pairs(arr) do
305                 local create = true
306
307                 for i,c in ipairs(self.children) do
308                         if c.option == key then
309                                 create = false
310                         end
311                 end
312
313                 if create and key:sub(1, 1) ~= "." then
314                         self:add_dynamic(key, true)
315                 end
316         end
317 end
318
319 -- Returns the section's UCI table
320 function AbstractSection.cfgvalue(self, section)
321         return self.map:get(section)
322 end
323
324 -- Removes the section
325 function AbstractSection.remove(self, section)
326         return self.map:del(section)
327 end
328
329 -- Creates the section
330 function AbstractSection.create(self, section)
331         return self.map:set(section, nil, self.sectiontype)
332 end
333
334
335
336 --[[
337 NamedSection - A fixed configuration section defined by its name
338 ]]--
339 NamedSection = class(AbstractSection)
340
341 function NamedSection.__init__(self, map, section, ...)
342         AbstractSection.__init__(self, map, ...)
343         self.template = "cbi/nsection"
344
345         self.section = section
346         self.addremove = false
347 end
348
349 function NamedSection.parse(self)
350         local s = self.section
351         local active = self:cfgvalue(s)
352
353
354         if self.addremove then
355                 local path = self.config.."."..s
356                 if active then -- Remove the section
357                         if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
358                                 return
359                         end
360                 else           -- Create and apply default values
361                         if luci.http.formvalue("cbi.cns."..path) and self:create(s) then
362                                 for k,v in pairs(self.children) do
363                                         v:write(s, v.default)
364                                 end
365                         end
366                 end
367         end
368
369         if active then
370                 AbstractSection.parse_dynamic(self, s)
371                 if luci.http.formvalue("cbi.submit") then
372                         Node.parse(self, s)
373                 end
374                 AbstractSection.parse_optionals(self, s)
375         end
376 end
377
378
379 --[[
380 TypedSection - A (set of) configuration section(s) defined by the type
381         addremove:      Defines whether the user can add/remove sections of this type
382         anonymous:  Allow creating anonymous sections
383         validate:       a validation function returning nil if the section is invalid
384 ]]--
385 TypedSection = class(AbstractSection)
386
387 function TypedSection.__init__(self, ...)
388         AbstractSection.__init__(self, ...)
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                 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         isnumber:       The value must be a valid (floating point) number
613         isinteger:  The value must be a valid integer
614         ispositive: The value must be positive (and a number)
615 ]]--
616 Value = class(AbstractValue)
617
618 function Value.__init__(self, ...)
619         AbstractValue.__init__(self, ...)
620         self.template  = "cbi/value"
621
622         self.maxlength  = nil
623         self.isnumber   = false
624         self.isinteger  = false
625 end
626
627 -- This validation is a bit more complex
628 function Value.validate(self, val)
629         if self.maxlength and tostring(val):len() > self.maxlength then
630                 val = nil
631         end
632
633         return luci.util.validate(val, self.isnumber, self.isinteger)
634 end
635
636
637 -- DummyValue - This does nothing except being there
638 DummyValue = class(AbstractValue)
639
640 function DummyValue.__init__(self, map, ...)
641         AbstractValue.__init__(self, map, ...)
642         self.template = "cbi/dvalue"
643         self.value = nil
644 end
645
646 function DummyValue.parse(self)
647
648 end
649
650 function DummyValue.render(self, s)
651         luci.template.render(self.template, {self=self, section=s})
652 end
653
654
655 --[[
656 Flag - A flag being enabled or disabled
657 ]]--
658 Flag = class(AbstractValue)
659
660 function Flag.__init__(self, ...)
661         AbstractValue.__init__(self, ...)
662         self.template  = "cbi/fvalue"
663
664         self.enabled = "1"
665         self.disabled = "0"
666 end
667
668 -- A flag can only have two states: set or unset
669 function Flag.parse(self, section)
670         local fvalue = self:formvalue(section)
671
672         if fvalue then
673                 fvalue = self.enabled
674         else
675                 fvalue = self.disabled
676         end
677
678         if fvalue == self.enabled or (not self.optional and not self.rmempty) then
679                 if not(fvalue == self:cfgvalue(section)) then
680                         self:write(section, fvalue)
681                 end
682         else
683                 self:remove(section)
684         end
685 end
686
687
688
689 --[[
690 ListValue - A one-line value predefined in a list
691         widget: The widget that will be used (select, radio)
692 ]]--
693 ListValue = class(AbstractValue)
694
695 function ListValue.__init__(self, ...)
696         AbstractValue.__init__(self, ...)
697         self.template  = "cbi/lvalue"
698         self.keylist = {}
699         self.vallist = {}
700
701         self.size   = 1
702         self.widget = "select"
703 end
704
705 function ListValue.value(self, key, val)
706         val = val or key
707         table.insert(self.keylist, tostring(key))
708         table.insert(self.vallist, tostring(val))
709 end
710
711 function ListValue.validate(self, val)
712         if luci.util.contains(self.keylist, val) then
713                 return val
714         else
715                 return nil
716         end
717 end
718
719
720
721 --[[
722 MultiValue - Multiple delimited values
723         widget: The widget that will be used (select, checkbox)
724         delimiter: The delimiter that will separate the values (default: " ")
725 ]]--
726 MultiValue = class(AbstractValue)
727
728 function MultiValue.__init__(self, ...)
729         AbstractValue.__init__(self, ...)
730         self.template = "cbi/mvalue"
731         self.keylist = {}
732         self.vallist = {}
733
734         self.widget = "checkbox"
735         self.delimiter = " "
736 end
737
738 function MultiValue.value(self, key, val)
739         val = val or key
740         table.insert(self.keylist, tostring(key))
741         table.insert(self.vallist, tostring(val))
742 end
743
744 function MultiValue.valuelist(self, section)
745         local val = self:cfgvalue(section)
746
747         if not(type(val) == "string") then
748                 return {}
749         end
750
751         return luci.util.split(val, self.delimiter)
752 end
753
754 function MultiValue.validate(self, val)
755         if not(type(val) == "string") then
756                 return nil
757         end
758
759         local result = ""
760
761         for value in val:gmatch("[^\n]+") do
762                 if luci.util.contains(self.keylist, value) then
763                         result = result .. self.delimiter .. value
764                 end
765         end
766
767         if result:len() > 0 then
768                 return result:sub(self.delimiter:len() + 1)
769         else
770                 return nil
771         end
772 end