Preliminary merged some barrier-free translations (thanks Alina Friedrichsen)
[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         if luci.http.formvalue("cbi.submit") then
264                 Node.parse(self, 1, ...)
265         end
266                 
267         local valid = true
268         for i, v in ipairs(self.children) do
269                 valid = valid 
270                  and (not v.tag_missing or not v.tag_missing[1])
271                  and (not v.tag_invalid or not v.tag_invalid[1])
272         end
273         
274         local state = 
275                 not luci.http.formvalue("cbi.submit") and 0
276                 or valid and 1
277                 or -1
278
279         self.dorender = self:handle(state, self.data)
280 end
281
282 function SimpleForm.render(self, ...)
283         if self.dorender then
284                 Node.render(self, ...)
285         end
286 end
287
288 -- Creates a child section
289 function SimpleForm.field(self, class, ...)
290         if instanceof(class, AbstractValue) then
291                 local obj  = class(self, ...)
292                 obj.track_missing = true
293                 self:append(obj)
294                 return obj
295         else
296                 error("class must be a descendent of AbstractValue")
297         end
298 end
299
300 function SimpleForm.set(self, section, option, value)
301         self.data[option] = value
302 end
303
304
305 function SimpleForm.del(self, section, option)
306         self.data[option] = nil
307 end
308
309
310 function SimpleForm.get(self, section, option)
311         return self.data[option]
312 end
313
314
315
316 --[[
317 AbstractSection
318 ]]--
319 AbstractSection = class(Node)
320
321 function AbstractSection.__init__(self, map, sectiontype, ...)
322         Node.__init__(self, ...)
323         self.sectiontype = sectiontype
324         self.map = map
325         self.config = map.config
326         self.optionals = {}
327         self.defaults = {}
328
329         self.optional = true
330         self.addremove = false
331         self.dynamic = false
332 end
333
334 -- Appends a new option
335 function AbstractSection.option(self, class, option, ...)
336         if instanceof(class, AbstractValue) then
337                 local obj  = class(self.map, option, ...)
338
339                 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
340
341                 self:append(obj)
342                 return obj
343         else
344                 error("class must be a descendent of AbstractValue")
345         end
346 end
347
348 -- Parse optional options
349 function AbstractSection.parse_optionals(self, section)
350         if not self.optional then
351                 return
352         end
353
354         self.optionals[section] = {}
355
356         local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
357         for k,v in ipairs(self.children) do
358                 if v.optional and not v:cfgvalue(section) then
359                         if field == v.option then
360                                 field = nil
361                         else
362                                 table.insert(self.optionals[section], v)
363                         end
364                 end
365         end
366
367         if field and #field > 0 and self.dynamic then
368                 self:add_dynamic(field)
369         end
370 end
371
372 -- Add a dynamic option
373 function AbstractSection.add_dynamic(self, field, optional)
374         local o = self:option(Value, field, field)
375         o.optional = optional
376 end
377
378 -- Parse all dynamic options
379 function AbstractSection.parse_dynamic(self, section)
380         if not self.dynamic then
381                 return
382         end
383
384         local arr  = luci.util.clone(self:cfgvalue(section))
385         local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
386         for k, v in pairs(form) do
387                 arr[k] = v
388         end
389
390         for key,val in pairs(arr) do
391                 local create = true
392
393                 for i,c in ipairs(self.children) do
394                         if c.option == key then
395                                 create = false
396                         end
397                 end
398
399                 if create and key:sub(1, 1) ~= "." then
400                         self:add_dynamic(key, true)
401                 end
402         end
403 end
404
405 -- Returns the section's UCI table
406 function AbstractSection.cfgvalue(self, section)
407         return self.map:get(section)
408 end
409
410 -- Removes the section
411 function AbstractSection.remove(self, section)
412         return self.map:del(section)
413 end
414
415 -- Creates the section
416 function AbstractSection.create(self, section)
417         local stat
418         
419         if section then
420                 stat = self.map:set(section, nil, self.sectiontype)
421         else
422                 section = self.map:add(self.sectiontype)
423                 stat = section
424         end
425
426         if stat then
427                 for k,v in pairs(self.children) do
428                         if v.default then
429                                 self.map:set(section, v.option, v.default)
430                         end
431                 end
432
433                 for k,v in pairs(self.defaults) do
434                         self.map:set(section, k, v)
435                 end
436         end
437
438         return stat
439 end
440
441
442
443 --[[
444 NamedSection - A fixed configuration section defined by its name
445 ]]--
446 NamedSection = class(AbstractSection)
447
448 function NamedSection.__init__(self, map, section, type, ...)
449         AbstractSection.__init__(self, map, type, ...)
450         Node._i18n(self, map.config, section, nil, ...)
451
452         self.template = "cbi/nsection"
453         self.section = section
454         self.addremove = false
455 end
456
457 function NamedSection.parse(self)
458         local s = self.section
459         local active = self:cfgvalue(s)
460
461
462         if self.addremove then
463                 local path = self.config.."."..s
464                 if active then -- Remove the section
465                         if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
466                                 return
467                         end
468                 else           -- Create and apply default values
469                         if luci.http.formvalue("cbi.cns."..path) then
470                                 self:create(s)
471                                 return
472                         end
473                 end
474         end
475
476         if active then
477                 AbstractSection.parse_dynamic(self, s)
478                 if luci.http.formvalue("cbi.submit") then
479                         Node.parse(self, s)
480                 end
481                 AbstractSection.parse_optionals(self, s)
482         end
483 end
484
485
486 --[[
487 TypedSection - A (set of) configuration section(s) defined by the type
488         addremove:      Defines whether the user can add/remove sections of this type
489         anonymous:  Allow creating anonymous sections
490         validate:       a validation function returning nil if the section is invalid
491 ]]--
492 TypedSection = class(AbstractSection)
493
494 function TypedSection.__init__(self, map, type, ...)
495         AbstractSection.__init__(self, map, type, ...)
496         Node._i18n(self, map.config, type, nil, ...)
497
498         self.template  = "cbi/tsection"
499         self.deps = {}
500
501         self.anonymous = false
502 end
503
504 -- Return all matching UCI sections for this TypedSection
505 function TypedSection.cfgsections(self)
506         local sections = {}
507         uci.foreach(self.map.config, self.sectiontype,
508                 function (section)
509                         if self:checkscope(section[".name"]) then
510                                 table.insert(sections, section[".name"])
511                         end
512                 end)
513
514         return sections
515 end
516
517 -- Limits scope to sections that have certain option => value pairs
518 function TypedSection.depends(self, option, value)
519         table.insert(self.deps, {option=option, value=value})
520 end
521
522 function TypedSection.parse(self)
523         if self.addremove then
524                 -- Create
525                 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
526                 local name  = luci.http.formvalue(crval)
527                 if self.anonymous then
528                         if name then
529                                 self:create()
530                         end
531                 else
532                         if name then
533                                 -- Ignore if it already exists
534                                 if self:cfgvalue(name) then
535                                         name = nil;
536                                 end
537
538                                 name = self:checkscope(name)
539
540                                 if not name then
541                                         self.err_invalid = true
542                                 end
543
544                                 if name and name:len() > 0 then
545                                         self:create(name)
546                                 end
547                         end
548                 end
549
550                 -- Remove
551                 crval = REMOVE_PREFIX .. self.config
552                 name = luci.http.formvaluetable(crval)
553                 for k,v in pairs(name) do
554                         if self:cfgvalue(k) and self:checkscope(k) then
555                                 self:remove(k)
556                         end
557                 end
558         end
559
560         for i, k in ipairs(self:cfgsections()) do
561                 AbstractSection.parse_dynamic(self, k)
562                 if luci.http.formvalue("cbi.submit") then
563                         Node.parse(self, k)
564                 end
565                 AbstractSection.parse_optionals(self, k)
566         end
567 end
568
569 -- Verifies scope of sections
570 function TypedSection.checkscope(self, section)
571         -- Check if we are not excluded
572         if self.filter and not self:filter(section) then
573                 return nil
574         end
575
576         -- Check if at least one dependency is met
577         if #self.deps > 0 and self:cfgvalue(section) then
578                 local stat = false
579
580                 for k, v in ipairs(self.deps) do
581                         if self:cfgvalue(section)[v.option] == v.value then
582                                 stat = true
583                         end
584                 end
585
586                 if not stat then
587                         return nil
588                 end
589         end
590
591         return self:validate(section)
592 end
593
594
595 -- Dummy validate function
596 function TypedSection.validate(self, section)
597         return section
598 end
599
600
601 --[[
602 AbstractValue - An abstract Value Type
603         null:           Value can be empty
604         valid:          A function returning the value if it is valid otherwise nil
605         depends:        A table of option => value pairs of which one must be true
606         default:        The default value
607         size:           The size of the input fields
608         rmempty:        Unset value if empty
609         optional:       This value is optional (see AbstractSection.optionals)
610 ]]--
611 AbstractValue = class(Node)
612
613 function AbstractValue.__init__(self, map, option, ...)
614         Node.__init__(self, ...)
615         self.option = option
616         self.map    = map
617         self.config = map.config
618         self.tag_invalid = {}
619         self.tag_missing = {}
620         self.deps = {}
621
622         self.track_missing = false
623         self.rmempty   = false
624         self.default   = nil
625         self.size      = nil
626         self.optional  = false
627         self.stateful  = false
628 end
629
630 -- Add a dependencie to another section field
631 function AbstractValue.depends(self, field, value)
632         table.insert(self.deps, {field=field, value=value})
633 end
634
635 -- Return whether this object should be created
636 function AbstractValue.formcreated(self, section)
637         local key = "cbi.opt."..self.config.."."..section
638         return (luci.http.formvalue(key) == self.option)
639 end
640
641 -- Returns the formvalue for this object
642 function AbstractValue.formvalue(self, section)
643         local key = "cbid."..self.map.config.."."..section.."."..self.option
644         return luci.http.formvalue(key)
645 end
646
647 function AbstractValue.additional(self, value)
648         self.optional = value
649 end
650
651 function AbstractValue.mandatory(self, value)
652         self.rmempty = not value
653 end
654
655 function AbstractValue.parse(self, section)
656         local fvalue = self:formvalue(section)
657         local cvalue = self:cfgvalue(section)
658
659         if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
660                 fvalue = self:transform(self:validate(fvalue, section))
661                 if not fvalue then
662                         self.tag_invalid[section] = true
663                 end
664                 if fvalue and not (fvalue == cvalue) then
665                         self:write(section, fvalue)
666                 end
667         else                                                    -- Unset the UCI or error
668                 if self.rmempty or self.optional then
669                         self:remove(section)
670                 elseif self.track_missing and not fvalue or fvalue ~= cvalue then
671                         self.tag_missing[section] = true
672                 end
673         end
674 end
675
676 -- Render if this value exists or if it is mandatory
677 function AbstractValue.render(self, s, scope)
678         if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
679                 scope = scope or {}
680                 scope.section = s
681                 scope.cbid    = "cbid." .. self.config ..
682                                 "."     .. s           ..
683                                                 "."     .. self.option
684
685                 scope.ifattr = function(cond,key,val)
686                         if cond then
687                                 return string.format(
688                                         ' %s="%s"', tostring(key),
689                                         tostring( val
690                                          or scope[key]
691                                          or (type(self[key]) ~= "function" and self[key])
692                                          or "" )
693                                 )
694                         else
695                                 return ''
696                         end
697                 end
698
699                 scope.attr = function(...)
700                         return scope.ifattr( true, ... )
701                 end
702
703                 Node.render(self, scope)
704         end
705 end
706
707 -- Return the UCI value of this object
708 function AbstractValue.cfgvalue(self, section)
709         return self.stateful
710          and self.map:stateget(section, self.option)
711          or  self.map:get(section, self.option)
712 end
713
714 -- Validate the form value
715 function AbstractValue.validate(self, value)
716         return value
717 end
718
719 AbstractValue.transform = AbstractValue.validate
720
721
722 -- Write to UCI
723 function AbstractValue.write(self, section, value)
724         return self.map:set(section, self.option, value)
725 end
726
727 -- Remove from UCI
728 function AbstractValue.remove(self, section)
729         return self.map:del(section, self.option)
730 end
731
732
733
734
735 --[[
736 Value - A one-line value
737         maxlength:      The maximum length
738 ]]--
739 Value = class(AbstractValue)
740
741 function Value.__init__(self, ...)
742         AbstractValue.__init__(self, ...)
743         self.template  = "cbi/value"
744         self.keylist = {}
745         self.vallist = {}
746 end
747
748 function Value.value(self, key, val)
749         val = val or key
750         table.insert(self.keylist, tostring(key))
751         table.insert(self.vallist, tostring(val))
752 end
753
754
755 -- DummyValue - This does nothing except being there
756 DummyValue = class(AbstractValue)
757
758 function DummyValue.__init__(self, map, ...)
759         AbstractValue.__init__(self, map, ...)
760         self.template = "cbi/dvalue"
761         self.value = nil
762 end
763
764 function DummyValue.parse(self)
765
766 end
767
768
769 --[[
770 Flag - A flag being enabled or disabled
771 ]]--
772 Flag = class(AbstractValue)
773
774 function Flag.__init__(self, ...)
775         AbstractValue.__init__(self, ...)
776         self.template  = "cbi/fvalue"
777
778         self.enabled = "1"
779         self.disabled = "0"
780 end
781
782 -- A flag can only have two states: set or unset
783 function Flag.parse(self, section)
784         local fvalue = self:formvalue(section)
785
786         if fvalue then
787                 fvalue = self.enabled
788         else
789                 fvalue = self.disabled
790         end
791
792         if fvalue == self.enabled or (not self.optional and not self.rmempty) then
793                 if not(fvalue == self:cfgvalue(section)) then
794                         self:write(section, fvalue)
795                 end
796         else
797                 self:remove(section)
798         end
799 end
800
801
802
803 --[[
804 ListValue - A one-line value predefined in a list
805         widget: The widget that will be used (select, radio)
806 ]]--
807 ListValue = class(AbstractValue)
808
809 function ListValue.__init__(self, ...)
810         AbstractValue.__init__(self, ...)
811         self.template  = "cbi/lvalue"
812         self.keylist = {}
813         self.vallist = {}
814
815         self.size   = 1
816         self.widget = "select"
817 end
818
819 function ListValue.value(self, key, val)
820         val = val or key
821         table.insert(self.keylist, tostring(key))
822         table.insert(self.vallist, tostring(val))
823 end
824
825 function ListValue.validate(self, val)
826         if luci.util.contains(self.keylist, val) then
827                 return val
828         else
829                 return nil
830         end
831 end
832
833
834
835 --[[
836 MultiValue - Multiple delimited values
837         widget: The widget that will be used (select, checkbox)
838         delimiter: The delimiter that will separate the values (default: " ")
839 ]]--
840 MultiValue = class(AbstractValue)
841
842 function MultiValue.__init__(self, ...)
843         AbstractValue.__init__(self, ...)
844         self.template = "cbi/mvalue"
845         self.keylist = {}
846         self.vallist = {}
847
848         self.widget = "checkbox"
849         self.delimiter = " "
850 end
851
852 function MultiValue.render(self, ...)
853         if self.widget == "select" and not self.size then
854                 self.size = #self.vallist
855         end
856
857         AbstractValue.render(self, ...)
858 end
859
860 function MultiValue.value(self, key, val)
861         val = val or key
862         table.insert(self.keylist, tostring(key))
863         table.insert(self.vallist, tostring(val))
864 end
865
866 function MultiValue.valuelist(self, section)
867         local val = self:cfgvalue(section)
868
869         if not(type(val) == "string") then
870                 return {}
871         end
872
873         return luci.util.split(val, self.delimiter)
874 end
875
876 function MultiValue.validate(self, val)
877         val = (type(val) == "table") and val or {val}
878
879         local result
880
881         for i, value in ipairs(val) do
882                 if luci.util.contains(self.keylist, value) then
883                         result = result and (result .. self.delimiter .. value) or value
884                 end
885         end
886
887         return result
888 end
889
890 --[[
891 TextValue - A multi-line value
892         rows:   Rows
893 ]]--
894 TextValue = class(AbstractValue)
895
896 function TextValue.__init__(self, ...)
897         AbstractValue.__init__(self, ...)
898         self.template  = "cbi/tvalue"
899 end