libs/cbi: Added magic ;-)
[project/luci.git] / libs / cbi / luasrc / cbi.lua
index b7097b5..ff33604 100644 (file)
@@ -2,7 +2,7 @@
 LuCI - Configuration Bind Interface
 
 Description:
 LuCI - Configuration Bind Interface
 
 Description:
-Offers an interface for binding confiugration values to certain
+Offers an interface for binding configuration values to certain
 data types. Supports value and range validation and basic dependencies.
 
 FileId:
 data types. Supports value and range validation and basic dependencies.
 
 FileId:
@@ -13,9 +13,9 @@ Copyright 2008 Steven Barth <steven@midlink.org>
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
-You may obtain a copy of the License at 
+You may obtain a copy of the License at
 
 
-       http://www.apache.org/licenses/LICENSE-2.0 
+       http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
@@ -31,37 +31,43 @@ require("luci.util")
 require("luci.http")
 require("luci.model.uci")
 
 require("luci.http")
 require("luci.model.uci")
 
+local uci        = luci.model.uci
 local class      = luci.util.class
 local instanceof = luci.util.instanceof
 
 local class      = luci.util.class
 local instanceof = luci.util.instanceof
 
+
 -- Loads a CBI map from given file, creating an environment and returns it
 -- Loads a CBI map from given file, creating an environment and returns it
-function load(cbimap)
+function load(cbimap, ...)
        require("luci.fs")
        require("luci.i18n")
        require("luci.config")
        require("luci.fs")
        require("luci.i18n")
        require("luci.config")
-       require("luci.sys")
-       
-       local cbidir = luci.sys.libpath() .. "/model/cbi/"
+       require("luci.util")
+
+       local cbidir = luci.util.libpath() .. "/model/cbi/"
        local func, err = loadfile(cbidir..cbimap..".lua")
        local func, err = loadfile(cbidir..cbimap..".lua")
-       
+
        if not func then
                return nil
        end
        if not func then
                return nil
        end
-       
+
        luci.i18n.loadc("cbi")
        luci.i18n.loadc("cbi")
-       
+
        luci.util.resfenv(func)
        luci.util.updfenv(func, luci.cbi)
        luci.util.extfenv(func, "translate", luci.i18n.translate)
        luci.util.resfenv(func)
        luci.util.updfenv(func, luci.cbi)
        luci.util.extfenv(func, "translate", luci.i18n.translate)
-       
-       local map = func()
-       
-       if not instanceof(map, Map) then
-               error("CBI map returns no valid map object!")
-               return nil
+       luci.util.extfenv(func, "translatef", luci.i18n.translatef)
+       luci.util.extfenv(func, "arg", {...})
+
+       local maps = {func()}
+
+       for i, map in ipairs(maps) do
+               if not instanceof(map, Map) then
+                       error("CBI map returns no valid map object!")
+                       return nil
+               end
        end
        end
-       
-       return map
+
+       return maps
 end
 
 -- Node pseudo abstract class
 end
 
 -- Node pseudo abstract class
@@ -74,6 +80,22 @@ function Node.__init__(self, title, description)
        self.template = "cbi/node"
 end
 
        self.template = "cbi/node"
 end
 
+-- i18n helper
+function Node._i18n(self, config, section, option, title, description)
+
+       -- i18n loaded?
+       if type(luci.i18n) == "table" then
+
+               local key = config:gsub("[^%w]+", "")
+
+               if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
+               if option  then key = key .. "_" .. option:lower():gsub("[^%w]+", "")  end
+
+               self.title = title or luci.i18n.translate( key, option or section or config )
+               self.description = description or luci.i18n.translate( key .. "_desc", "" )
+       end
+end
+
 -- Append child nodes
 function Node.append(self, obj)
        table.insert(self.children, obj)
 -- Append child nodes
 function Node.append(self, obj)
        table.insert(self.children, obj)
@@ -114,26 +136,53 @@ end
 
 
 --[[
 
 
 --[[
-Map - A map describing a configuration file 
+Map - A map describing a configuration file
 ]]--
 Map = class(Node)
 
 function Map.__init__(self, config, ...)
        Node.__init__(self, ...)
 ]]--
 Map = class(Node)
 
 function Map.__init__(self, config, ...)
        Node.__init__(self, ...)
+       Node._i18n(self, config, nil, nil, ...)
+
        self.config = config
        self.config = config
+       self.parsechain = {self.config}
        self.template = "cbi/map"
        self.template = "cbi/map"
-       self.uci = luci.model.uci.Session()
-       self.ucidata, self.uciorder = self.uci:sections(self.config)
-       if not self.ucidata or not self.uciorder then
+       if not uci.load(self.config) then
                error("Unable to read UCI data: " .. self.config)
        end
 end
 
                error("Unable to read UCI data: " .. self.config)
        end
 end
 
+
+-- Chain foreign config
+function Map.chain(self, config)
+       table.insert(self.parsechain, config)
+end
+
 -- Use optimized UCI writing
 function Map.parse(self, ...)
 -- Use optimized UCI writing
 function Map.parse(self, ...)
-       self.uci:t_load(self.config)
        Node.parse(self, ...)
        Node.parse(self, ...)
-       self.uci:t_save(self.config)
+       for i, config in ipairs(self.parsechain) do
+               uci.save(config)
+       end
+       if luci.http.formvalue("cbi.apply") then
+               for i, config in ipairs(self.parsechain) do
+                       uci.commit(config)
+                       if luci.config.uci_oncommit and luci.config.uci_oncommit[config] then
+                               luci.util.exec(luci.config.uci_oncommit[config])
+                       end
+
+                       -- Refresh data because commit changes section names
+                       uci.unload(config)
+                       uci.load(config)
+               end
+
+               -- Reparse sections
+               Node.parse(self, ...)
+
+       end
+       for i, config in ipairs(self.parsechain) do
+               uci.unload(config)
+       end
 end
 
 -- Creates a child section
 end
 
 -- Creates a child section
@@ -149,59 +198,35 @@ end
 
 -- UCI add
 function Map.add(self, sectiontype)
 
 -- UCI add
 function Map.add(self, sectiontype)
-       local name = self.uci:t_add(self.config, sectiontype)
-       if name then
-               self.ucidata[name] = {}
-               self.ucidata[name][".type"] = sectiontype
-               table.insert(self.uciorder, name)
-       end
-       return name
+       return uci.add(self.config, sectiontype)
 end
 
 -- UCI set
 function Map.set(self, section, option, value)
 end
 
 -- UCI set
 function Map.set(self, section, option, value)
-       local stat = self.uci:t_set(self.config, section, option, value)
-       if stat then
-               local val = self.uci:t_get(self.config, section, option)
-               if option then
-                       self.ucidata[section][option] = val
-               else
-                       if not self.ucidata[section] then
-                               self.ucidata[section] = {}
-                       end
-                       self.ucidata[section][".type"] = val
-                       table.insert(self.uciorder, section)
-               end
+       if option then
+               return uci.set(self.config, section, option, value)
+       else
+               return uci.set(self.config, section, value)
        end
        end
-       return stat
 end
 
 -- UCI del
 function Map.del(self, section, option)
 end
 
 -- UCI del
 function Map.del(self, section, option)
-       local stat = self.uci:t_del(self.config, section, option)
-       if stat then
-               if option then
-                       self.ucidata[section][option] = nil
-               else
-                       self.ucidata[section] = nil
-                       for i, k in ipairs(self.uciorder) do
-                               if section == k then
-                                       table.remove(self.uciorder, i)
-                               end
-                       end
-               end
+       if option then
+               return uci.delete(self.config, section, option)
+       else
+               return uci.delete(self.config, section)
        end
        end
-       return stat
 end
 
 end
 
--- UCI get (cached)
+-- UCI get
 function Map.get(self, section, option)
        if not section then
 function Map.get(self, section, option)
        if not section then
-               return self.ucidata, self.uciorder
-       elseif option and self.ucidata[section] then
-               return self.ucidata[section][option]
+               return uci.get_all(self.config)
+       elseif option then
+               return uci.get(self.config, section, option)
        else
        else
-               return self.ucidata[section]
+               return uci.get_all(self.config, section)
        end
 end
 
        end
 end
 
@@ -217,21 +242,25 @@ function AbstractSection.__init__(self, map, sectiontype, ...)
        self.map = map
        self.config = map.config
        self.optionals = {}
        self.map = map
        self.config = map.config
        self.optionals = {}
-       
+       self.defaults = {}
+
        self.optional = true
        self.addremove = false
        self.dynamic = false
 end
 
 -- Appends a new option
        self.optional = true
        self.addremove = false
        self.dynamic = false
 end
 
 -- Appends a new option
-function AbstractSection.option(self, class, ...)
+function AbstractSection.option(self, class, option, ...)
        if instanceof(class, AbstractValue) then
        if instanceof(class, AbstractValue) then
-               local obj  = class(self.map, ...)
+               local obj  = class(self.map, option, ...)
+
+               Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
+
                self:append(obj)
                return obj
        else
                error("class must be a descendent of AbstractValue")
                self:append(obj)
                return obj
        else
                error("class must be a descendent of AbstractValue")
-       end     
+       end
 end
 
 -- Parse optional options
 end
 
 -- Parse optional options
@@ -239,9 +268,9 @@ function AbstractSection.parse_optionals(self, section)
        if not self.optional then
                return
        end
        if not self.optional then
                return
        end
-       
+
        self.optionals[section] = {}
        self.optionals[section] = {}
-       
+
        local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
        for k,v in ipairs(self.children) do
                if v.optional and not v:cfgvalue(section) then
        local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
        for k,v in ipairs(self.children) do
                if v.optional and not v:cfgvalue(section) then
@@ -252,7 +281,7 @@ function AbstractSection.parse_optionals(self, section)
                        end
                end
        end
                        end
                end
        end
-       
+
        if field and #field > 0 and self.dynamic then
                self:add_dynamic(field)
        end
        if field and #field > 0 and self.dynamic then
                self:add_dynamic(field)
        end
@@ -269,27 +298,27 @@ function AbstractSection.parse_dynamic(self, section)
        if not self.dynamic then
                return
        end
        if not self.dynamic then
                return
        end
-       
+
        local arr  = luci.util.clone(self:cfgvalue(section))
        local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
        for k, v in pairs(form) do
                arr[k] = v
        end
        local arr  = luci.util.clone(self:cfgvalue(section))
        local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
        for k, v in pairs(form) do
                arr[k] = v
        end
-       
+
        for key,val in pairs(arr) do
                local create = true
        for key,val in pairs(arr) do
                local create = true
-               
+
                for i,c in ipairs(self.children) do
                        if c.option == key then
                                create = false
                        end
                end
                for i,c in ipairs(self.children) do
                        if c.option == key then
                                create = false
                        end
                end
-               
+
                if create and key:sub(1, 1) ~= "." then
                        self:add_dynamic(key, true)
                end
        end
                if create and key:sub(1, 1) ~= "." then
                        self:add_dynamic(key, true)
                end
        end
-end    
+end
 
 -- Returns the section's UCI table
 function AbstractSection.cfgvalue(self, section)
 
 -- Returns the section's UCI table
 function AbstractSection.cfgvalue(self, section)
@@ -303,7 +332,28 @@ end
 
 -- Creates the section
 function AbstractSection.create(self, section)
 
 -- Creates the section
 function AbstractSection.create(self, section)
-       return self.map:set(section, nil, self.sectiontype)
+       local stat
+       
+       if section then
+               stat = self.map:set(section, nil, self.sectiontype)
+       else
+               section = self.map:add(self.sectiontype)
+               stat = section
+       end
+
+       if stat then
+               for k,v in pairs(self.children) do
+                       if v.default then
+                               self.map:set(section, v.option, v.default)
+                       end
+               end
+
+               for k,v in pairs(self.defaults) do
+                       self.map:set(section, k, v)
+               end
+       end
+
+       return stat
 end
 
 
 end
 
 
@@ -313,19 +363,20 @@ NamedSection - A fixed configuration section defined by its name
 ]]--
 NamedSection = class(AbstractSection)
 
 ]]--
 NamedSection = class(AbstractSection)
 
-function NamedSection.__init__(self, map, section, ...)
-       AbstractSection.__init__(self, map, ...)
+function NamedSection.__init__(self, map, section, type, ...)
+       AbstractSection.__init__(self, map, type, ...)
+       Node._i18n(self, map.config, section, nil, ...)
+
        self.template = "cbi/nsection"
        self.template = "cbi/nsection"
-       
        self.section = section
        self.addremove = false
 end
 
 function NamedSection.parse(self)
        self.section = section
        self.addremove = false
 end
 
 function NamedSection.parse(self)
-       local s = self.section  
+       local s = self.section
        local active = self:cfgvalue(s)
        local active = self:cfgvalue(s)
-       
-       
+
+
        if self.addremove then
                local path = self.config.."."..s
                if active then -- Remove the section
        if self.addremove then
                local path = self.config.."."..s
                if active then -- Remove the section
@@ -333,21 +384,20 @@ function NamedSection.parse(self)
                                return
                        end
                else           -- Create and apply default values
                                return
                        end
                else           -- Create and apply default values
-                       if luci.http.formvalue("cbi.cns."..path) and self:create(s) then
-                               for k,v in pairs(self.children) do
-                                       v:write(s, v.default)
-                               end
+                       if luci.http.formvalue("cbi.cns."..path) then
+                               self:create(s)
+                               return
                        end
                end
        end
                        end
                end
        end
-       
+
        if active then
                AbstractSection.parse_dynamic(self, s)
                if luci.http.formvalue("cbi.submit") then
                        Node.parse(self, s)
                end
                AbstractSection.parse_optionals(self, s)
        if active then
                AbstractSection.parse_dynamic(self, s)
                if luci.http.formvalue("cbi.submit") then
                        Node.parse(self, s)
                end
                AbstractSection.parse_optionals(self, s)
-       end     
+       end
 end
 
 
 end
 
 
@@ -355,48 +405,31 @@ end
 TypedSection - A (set of) configuration section(s) defined by the type
        addremove:      Defines whether the user can add/remove sections of this type
        anonymous:  Allow creating anonymous sections
 TypedSection - A (set of) configuration section(s) defined by the type
        addremove:      Defines whether the user can add/remove sections of this type
        anonymous:  Allow creating anonymous sections
-       validate:       a validation function returning nil if the section is invalid 
+       validate:       a validation function returning nil if the section is invalid
 ]]--
 TypedSection = class(AbstractSection)
 
 ]]--
 TypedSection = class(AbstractSection)
 
-function TypedSection.__init__(self, ...)
-       AbstractSection.__init__(self, ...)
+function TypedSection.__init__(self, map, type, ...)
+       AbstractSection.__init__(self, map, type, ...)
+       Node._i18n(self, map.config, type, nil, ...)
+
        self.template  = "cbi/tsection"
        self.deps = {}
        self.template  = "cbi/tsection"
        self.deps = {}
-       self.excludes = {}
-       
+
        self.anonymous = false
 end
 
 -- Return all matching UCI sections for this TypedSection
 function TypedSection.cfgsections(self)
        local sections = {}
        self.anonymous = false
 end
 
 -- Return all matching UCI sections for this TypedSection
 function TypedSection.cfgsections(self)
        local sections = {}
-       local map, order = self.map:get()
-       
-       for i, k in ipairs(order) do
-               if map[k][".type"] == self.sectiontype then
-                       if self:checkscope(k) then
-                               table.insert(sections, k)
+       uci.foreach(self.map.config, self.sectiontype,
+               function (section)
+                       if self:checkscope(section[".name"]) then
+                               table.insert(sections, section[".name"])
                        end
                        end
-               end
-       end
-       
-       return sections 
-end
+               end)
 
 
--- Creates a new section of this type with the given name (or anonymous)
-function TypedSection.create(self, name)
-       if name then    
-               self.map:set(name, nil, self.sectiontype)
-       else
-               name = self.map:add(self.sectiontype)
-       end
-       
-       for k,v in pairs(self.children) do
-               if v.default then
-                       self.map:set(name, v.option, v.default)
-               end
-       end
+       return sections
 end
 
 -- Limits scope to sections that have certain option => value pairs
 end
 
 -- Limits scope to sections that have certain option => value pairs
@@ -404,11 +437,6 @@ function TypedSection.depends(self, option, value)
        table.insert(self.deps, {option=option, value=value})
 end
 
        table.insert(self.deps, {option=option, value=value})
 end
 
--- Excludes several sections by name
-function TypedSection.exclude(self, field)
-       self.excludes[field] = true
-end
-
 function TypedSection.parse(self)
        if self.addremove then
                -- Create
 function TypedSection.parse(self)
        if self.addremove then
                -- Create
@@ -418,25 +446,25 @@ function TypedSection.parse(self)
                        if name then
                                self:create()
                        end
                        if name then
                                self:create()
                        end
-               else            
+               else
                        if name then
                                -- Ignore if it already exists
                                if self:cfgvalue(name) then
                                        name = nil;
                                end
                        if name then
                                -- Ignore if it already exists
                                if self:cfgvalue(name) then
                                        name = nil;
                                end
-                               
+
                                name = self:checkscope(name)
                                name = self:checkscope(name)
-                               
+
                                if not name then
                                        self.err_invalid = true
                                if not name then
                                        self.err_invalid = true
-                               end             
-                               
+                               end
+
                                if name and name:len() > 0 then
                                        self:create(name)
                                end
                        end
                end
                                if name and name:len() > 0 then
                                        self:create(name)
                                end
                        end
                end
-               
+
                -- Remove
                crval = "cbi.rts." .. self.config
                name = luci.http.formvaluetable(crval)
                -- Remove
                crval = "cbi.rts." .. self.config
                name = luci.http.formvaluetable(crval)
@@ -444,9 +472,9 @@ function TypedSection.parse(self)
                        if self:cfgvalue(k) and self:checkscope(k) then
                                self:remove(k)
                        end
                        if self:cfgvalue(k) and self:checkscope(k) then
                                self:remove(k)
                        end
-               end     
+               end
        end
        end
-       
+
        for i, k in ipairs(self:cfgsections()) do
                AbstractSection.parse_dynamic(self, k)
                if luci.http.formvalue("cbi.submit") then
        for i, k in ipairs(self:cfgsections()) do
                AbstractSection.parse_dynamic(self, k)
                if luci.http.formvalue("cbi.submit") then
@@ -459,25 +487,25 @@ end
 -- Verifies scope of sections
 function TypedSection.checkscope(self, section)
        -- Check if we are not excluded
 -- Verifies scope of sections
 function TypedSection.checkscope(self, section)
        -- Check if we are not excluded
-       if self.excludes[section] then
+       if self.filter and not self.filter(section) then
                return nil
        end
                return nil
        end
-       
+
        -- Check if at least one dependency is met
        if #self.deps > 0 and self:cfgvalue(section) then
                local stat = false
        -- Check if at least one dependency is met
        if #self.deps > 0 and self:cfgvalue(section) then
                local stat = false
-               
+
                for k, v in ipairs(self.deps) do
                        if self:cfgvalue(section)[v.option] == v.value then
                                stat = true
                        end
                end
                for k, v in ipairs(self.deps) do
                        if self:cfgvalue(section)[v.option] == v.value then
                                stat = true
                        end
                end
-               
+
                if not stat then
                        return nil
                end
        end
                if not stat then
                        return nil
                end
        end
-       
+
        return self:validate(section)
 end
 
        return self:validate(section)
 end
 
@@ -491,7 +519,7 @@ end
 --[[
 AbstractValue - An abstract Value Type
        null:           Value can be empty
 --[[
 AbstractValue - An abstract Value Type
        null:           Value can be empty
-       valid:          A function returning the value if it is valid otherwise nil 
+       valid:          A function returning the value if it is valid otherwise nil
        depends:        A table of option => value pairs of which one must be true
        default:        The default value
        size:           The size of the input fields
        depends:        A table of option => value pairs of which one must be true
        default:        The default value
        size:           The size of the input fields
@@ -507,7 +535,7 @@ function AbstractValue.__init__(self, map, option, ...)
        self.config = map.config
        self.tag_invalid = {}
        self.deps = {}
        self.config = map.config
        self.tag_invalid = {}
        self.deps = {}
-       
+
        self.rmempty  = false
        self.default  = nil
        self.size     = nil
        self.rmempty  = false
        self.default  = nil
        self.size     = nil
@@ -533,7 +561,7 @@ end
 
 function AbstractValue.parse(self, section)
        local fvalue = self:formvalue(section)
 
 function AbstractValue.parse(self, section)
        local fvalue = self:formvalue(section)
-       
+
        if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
                fvalue = self:validate(fvalue)
                if not fvalue then
        if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
                fvalue = self:validate(fvalue)
                if not fvalue then
@@ -541,7 +569,7 @@ function AbstractValue.parse(self, section)
                end
                if fvalue and not (fvalue == self:cfgvalue(section)) then
                        self:write(section, fvalue)
                end
                if fvalue and not (fvalue == self:cfgvalue(section)) then
                        self:write(section, fvalue)
-               end 
+               end
        else                                                    -- Unset the UCI or error
                if self.rmempty or self.optional then
                        self:remove(section)
        else                                                    -- Unset the UCI or error
                if self.rmempty or self.optional then
                        self:remove(section)
@@ -554,6 +582,28 @@ function AbstractValue.render(self, s, scope)
        if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
                scope = scope or {}
                scope.section = s
        if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
                scope = scope or {}
                scope.section = s
+               scope.cbid    = "cbid." .. self.config ..
+                               "."     .. s           ..
+                                               "."     .. self.option
+
+               scope.ifattr = function(cond,key,val)
+                       if cond then
+                               return string.format(
+                                       ' %s="%s"', tostring(key),
+                                       tostring( val
+                                        or scope[key]
+                                        or (type(self[key]) ~= "function" and self[key])
+                                        or "" )
+                               )
+                       else
+                               return ''
+                       end
+               end
+
+               scope.attr = function(...)
+                       return scope.ifattr( true, ... )
+               end
+
                Node.render(self, scope)
        end
 end
                Node.render(self, scope)
        end
 end
@@ -584,28 +634,20 @@ end
 --[[
 Value - A one-line value
        maxlength:      The maximum length
 --[[
 Value - A one-line value
        maxlength:      The maximum length
-       isnumber:       The value must be a valid (floating point) number
-       isinteger:  The value must be a valid integer
-       ispositive: The value must be positive (and a number)
 ]]--
 Value = class(AbstractValue)
 
 function Value.__init__(self, ...)
        AbstractValue.__init__(self, ...)
        self.template  = "cbi/value"
 ]]--
 Value = class(AbstractValue)
 
 function Value.__init__(self, ...)
        AbstractValue.__init__(self, ...)
        self.template  = "cbi/value"
-       
-       self.maxlength  = nil
-       self.isnumber   = false
-       self.isinteger  = false
+       self.keylist = {}
+       self.vallist = {}
 end
 
 end
 
--- This validation is a bit more complex
-function Value.validate(self, val)
-       if self.maxlength and tostring(val):len() > self.maxlength then
-               val = nil
-       end
-       
-       return luci.util.validate(val, self.isnumber, self.isinteger)
+function Value.value(self, key, val)
+       val = val or key
+       table.insert(self.keylist, tostring(key))
+       table.insert(self.vallist, tostring(val))
 end
 
 
 end
 
 
@@ -619,11 +661,7 @@ function DummyValue.__init__(self, map, ...)
 end
 
 function DummyValue.parse(self)
 end
 
 function DummyValue.parse(self)
-       
-end
 
 
-function DummyValue.render(self, s)
-       luci.template.render(self.template, {self=self, section=s})
 end
 
 
 end
 
 
@@ -635,7 +673,7 @@ Flag = class(AbstractValue)
 function Flag.__init__(self, ...)
        AbstractValue.__init__(self, ...)
        self.template  = "cbi/fvalue"
 function Flag.__init__(self, ...)
        AbstractValue.__init__(self, ...)
        self.template  = "cbi/fvalue"
-       
+
        self.enabled = "1"
        self.disabled = "0"
 end
        self.enabled = "1"
        self.disabled = "0"
 end
@@ -643,17 +681,17 @@ end
 -- A flag can only have two states: set or unset
 function Flag.parse(self, section)
        local fvalue = self:formvalue(section)
 -- A flag can only have two states: set or unset
 function Flag.parse(self, section)
        local fvalue = self:formvalue(section)
-       
+
        if fvalue then
                fvalue = self.enabled
        else
                fvalue = self.disabled
        if fvalue then
                fvalue = self.enabled
        else
                fvalue = self.disabled
-       end     
-       
-       if fvalue == self.enabled or (not self.optional and not self.rmempty) then              
+       end
+
+       if fvalue == self.enabled or (not self.optional and not self.rmempty) then
                if not(fvalue == self:cfgvalue(section)) then
                        self:write(section, fvalue)
                if not(fvalue == self:cfgvalue(section)) then
                        self:write(section, fvalue)
-               end 
+               end
        else
                self:remove(section)
        end
        else
                self:remove(section)
        end
@@ -672,7 +710,7 @@ function ListValue.__init__(self, ...)
        self.template  = "cbi/lvalue"
        self.keylist = {}
        self.vallist = {}
        self.template  = "cbi/lvalue"
        self.keylist = {}
        self.vallist = {}
-       
+
        self.size   = 1
        self.widget = "select"
 end
        self.size   = 1
        self.widget = "select"
 end
@@ -680,7 +718,7 @@ end
 function ListValue.value(self, key, val)
        val = val or key
        table.insert(self.keylist, tostring(key))
 function ListValue.value(self, key, val)
        val = val or key
        table.insert(self.keylist, tostring(key))
-       table.insert(self.vallist, tostring(val)) 
+       table.insert(self.vallist, tostring(val))
 end
 
 function ListValue.validate(self, val)
 end
 
 function ListValue.validate(self, val)
@@ -704,44 +742,46 @@ function MultiValue.__init__(self, ...)
        AbstractValue.__init__(self, ...)
        self.template = "cbi/mvalue"
        self.keylist = {}
        AbstractValue.__init__(self, ...)
        self.template = "cbi/mvalue"
        self.keylist = {}
-       self.vallist = {}       
-       
+       self.vallist = {}
+
        self.widget = "checkbox"
        self.delimiter = " "
 end
 
        self.widget = "checkbox"
        self.delimiter = " "
 end
 
+function MultiValue.render(self, ...)
+       if self.widget == "select" and not self.size then
+               self.size = #self.vallist
+       end
+
+       AbstractValue.render(self, ...)
+end
+
 function MultiValue.value(self, key, val)
        val = val or key
        table.insert(self.keylist, tostring(key))
 function MultiValue.value(self, key, val)
        val = val or key
        table.insert(self.keylist, tostring(key))
-       table.insert(self.vallist, tostring(val)) 
+       table.insert(self.vallist, tostring(val))
 end
 
 function MultiValue.valuelist(self, section)
        local val = self:cfgvalue(section)
 end
 
 function MultiValue.valuelist(self, section)
        local val = self:cfgvalue(section)
-       
+
        if not(type(val) == "string") then
                return {}
        end
        if not(type(val) == "string") then
                return {}
        end
-       
+
        return luci.util.split(val, self.delimiter)
 end
 
 function MultiValue.validate(self, val)
        return luci.util.split(val, self.delimiter)
 end
 
 function MultiValue.validate(self, val)
-       if not(type(val) == "string") then
-               return nil
-       end
-       
-       local result = ""
-       
-       for value in val:gmatch("[^\n]+") do
+       val = (type(val) == "table") and val or {val}
+
+       local result
+
+       for i, value in ipairs(val) do
                if luci.util.contains(self.keylist, value) then
                if luci.util.contains(self.keylist, value) then
-                       result = result .. self.delimiter .. value
-               end 
-       end
-       
-       if result:len() > 0 then
-               return result:sub(self.delimiter:len() + 1)
-       else
-               return nil
+                       result = result and (result .. self.delimiter .. value) or value
+               end
        end
        end
-end
\ No newline at end of file
+
+       return result
+end