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