Propagate CBI status via HTTP-Header
[project/luci.git] / libs / cbi / luasrc / cbi.lua
index fb0355e..f54d5ac 100644 (file)
@@ -30,6 +30,7 @@ require("luci.template")
 require("luci.util")
 require("luci.http")
 require("luci.uvl")
 require("luci.util")
 require("luci.http")
 require("luci.uvl")
+require("luci.fs")
 
 local uci        = require("luci.model.uci")
 local class      = luci.util.class
 
 local uci        = require("luci.model.uci")
 local class      = luci.util.class
@@ -38,6 +39,7 @@ local instanceof = luci.util.instanceof
 FORM_NODATA  =  0
 FORM_VALID   =  1
 FORM_INVALID = -1
 FORM_NODATA  =  0
 FORM_VALID   =  1
 FORM_INVALID = -1
+FORM_CHANGED =  2
 
 AUTO = true
 
 
 AUTO = true
 
@@ -47,31 +49,91 @@ REMOVE_PREFIX = "cbi.rts."
 -- Loads a CBI map from given file, creating an environment and returns it
 function load(cbimap, ...)
        require("luci.fs")
 -- Loads a CBI map from given file, creating an environment and returns it
 function load(cbimap, ...)
        require("luci.fs")
-       require("luci.i18n")
+       local i18n = require "luci.i18n"
        require("luci.config")
        require("luci.util")
 
        require("luci.config")
        require("luci.util")
 
+       local upldir = "/lib/uci/upload/"
        local cbidir = luci.util.libpath() .. "/model/cbi/"
        local func, err = loadfile(cbimap) or loadfile(cbidir..cbimap..".lua")
        assert(func, err)
 
        luci.i18n.loadc("cbi")
        local cbidir = luci.util.libpath() .. "/model/cbi/"
        local func, err = loadfile(cbimap) or loadfile(cbidir..cbimap..".lua")
        assert(func, err)
 
        luci.i18n.loadc("cbi")
+       luci.i18n.loadc("uvl")
 
 
-       luci.util.resfenv(func)
-       luci.util.updfenv(func, luci.cbi)
-       luci.util.extfenv(func, "translate", luci.i18n.translate)
-       luci.util.extfenv(func, "translatef", luci.i18n.translatef)
-       luci.util.extfenv(func, "arg", {...})
+       local env = {
+               translate=i18n.translate,
+               translatef=i18n.translatef,
+               arg={...}
+       }
 
 
-       local maps = {func()}
+       setfenv(func, setmetatable(env, {__index =
+               function(tbl, key)
+                       return rawget(tbl, key) or _M[key] or _G[key]
+               end}))
+
+       local maps       = { func() }
+       local uploads    = { }
+       local has_upload = false
 
        for i, map in ipairs(maps) do
                if not instanceof(map, Node) then
                        error("CBI map returns no valid map object!")
                        return nil
 
        for i, map in ipairs(maps) do
                if not instanceof(map, Node) then
                        error("CBI map returns no valid map object!")
                        return nil
+               else
+                       map:prepare()
+                       if map.upload_fields then
+                               has_upload = true
+                               for _, field in ipairs(map.upload_fields) do
+                                       uploads[
+                                               field.config .. '.' ..
+                                               field.section.sectiontype .. '.' ..
+                                               field.option
+                                       ] = true
+                               end
+                       end
                end
        end
 
                end
        end
 
+       if has_upload then
+               local uci = luci.model.uci.cursor()
+               local prm = luci.http.context.request.message.params
+               local fd, cbid
+
+               luci.http.setfilehandler(
+                       function( field, chunk, eof )
+                               if not field then return end
+                               if field.name and not cbid then
+                                       local c, s, o = field.name:gmatch(
+                                               "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
+                                       )()
+
+                                       if c and s and o then
+                                               local t = uci:get( c, s )
+                                               if t and uploads[c.."."..t.."."..o] then
+                                                       local path = upldir .. field.name
+                                                       fd = io.open(path, "w")
+                                                       if fd then
+                                                               cbid = field.name
+                                                               prm[cbid] = path
+                                                       end
+                                               end
+                                       end
+                               end
+
+                               if field.name == cbid and fd then
+                                       fd:write(chunk)
+                               end
+
+                               if eof and fd then
+                                       fd:close()
+                                       fd   = nil
+                                       cbid = nil
+                               end
+                       end
+               )
+       end
+
        return maps
 end
 
        return maps
 end
 
@@ -81,28 +143,43 @@ local function _uvl_validate_section(node, name)
        luci.uvl.STRICT_UNKNOWN_OPTIONS = false
        luci.uvl.STRICT_UNKNOWN_SECTIONS = false
 
        luci.uvl.STRICT_UNKNOWN_OPTIONS = false
        luci.uvl.STRICT_UNKNOWN_SECTIONS = false
 
-       local stat, err = node.map.validator:validate_section(node.config, name, co)
-       if err then
-               node.map.save = false
-               if err:is(luci.uvl.errors.ERR_DEPENDENCY) then
-                       node.tag_deperror[name] = true
-               else
-                       node.tag_invalid[name] = true
+       local function tag_fields(e)
+               if e.option and node.fields[e.option] then
+                       if node.fields[e.option].error then
+                               node.fields[e.option].error[name] = e
+                       else
+                               node.fields[e.option].error = { [name] = e }
+                       end
+               elseif e.childs then
+                       for _, c in ipairs(e.childs) do tag_fields(c) end
                end
                end
-               for i, v in ipairs(err.childs) do
-                       if v.option and node.fields[v.option] then
-                               if v:is(luci.uvl.errors.ERR_DEPENDENCY) then
-                                       node.fields[v.option].tag_reqerror[name] = true
-                               elseif v:is(luci.uvl.errors.ERR_OPT_REQUIRED) then
-                                       node.fields[v.option].tag_missing[name] = true
-                                       node.tag_deperror[name] = true
-                               elseif v:is(luci.uvl.errors.ERR_OPTION) then
-                                       node.fields[v.option].tag_invalid[name] = true
-                               end
+       end
+
+       local function tag_section(e)
+               local s = { }
+               for _, c in ipairs(e.childs) do
+                       if c.childs and not c:is(luci.uvl.errors.ERR_DEPENDENCY) then
+                               table.insert( s, c.childs[1]:string() )
+                       else
+                               table.insert( s, c:string() )
+                       end
+               end
+               if #s > 0 then
+                       if node.error then
+                               node.error[name] = s
+                       else
+                               node.error = { [name] = s }
                        end
                end
        end
 
                        end
                end
        end
 
+       local stat, err = node.map.validator:validate_section(node.config, name, co)
+       if err then
+               node.map.save = false
+               tag_fields(err)
+               tag_section(err)
+       end
+
 end
 
 local function _uvl_strip_remote_dependencies(deps)
 end
 
 local function _uvl_strip_remote_dependencies(deps)
@@ -145,6 +222,13 @@ function Node._i18n(self, config, section, option, title, description)
        end
 end
 
        end
 end
 
+-- Prepare nodes
+function Node.prepare(self, ...)
+       for k, child in ipairs(self.children) do
+               child:prepare(...)
+       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)
@@ -200,6 +284,7 @@ function Map.__init__(self, config, ...)
        self.config = config
        self.parsechain = {self.config}
        self.template = "cbi/map"
        self.config = config
        self.parsechain = {self.config}
        self.template = "cbi/map"
+       self.apply_on_parse = nil
        self.uci = uci.cursor()
        self.save = true
        if not self.uci:load(self.config) then
        self.uci = uci.cursor()
        self.save = true
        if not self.uci:load(self.config) then
@@ -208,7 +293,7 @@ function Map.__init__(self, config, ...)
 
        self.validator = luci.uvl.UVL()
        self.scheme = self.validator:get_scheme(self.config)
 
        self.validator = luci.uvl.UVL()
        self.scheme = self.validator:get_scheme(self.config)
-       
+
 end
 
 function Map.get_scheme(self, sectiontype, option)
 end
 
 function Map.get_scheme(self, sectiontype, option)
@@ -220,6 +305,9 @@ function Map.get_scheme(self, sectiontype, option)
        end
 end
 
        end
 end
 
+function Map.submitstate(self)
+       return luci.http.formvalue("cbi.submit")
+end
 
 -- Chain foreign config
 function Map.chain(self, config)
 
 -- Chain foreign config
 function Map.chain(self, config)
@@ -227,8 +315,8 @@ function Map.chain(self, config)
 end
 
 -- Use optimized UCI writing
 end
 
 -- Use optimized UCI writing
-function Map.parse(self, ...)
-       Node.parse(self, ...)
+function Map.parse(self)
+       Node.parse(self)
 
        if self.save then
                for i, config in ipairs(self.parsechain) do
 
        if self.save then
                for i, config in ipairs(self.parsechain) do
@@ -237,19 +325,48 @@ function Map.parse(self, ...)
                if luci.http.formvalue("cbi.apply") then
                        for i, config in ipairs(self.parsechain) do
                                self.uci:commit(config)
                if luci.http.formvalue("cbi.apply") then
                        for i, config in ipairs(self.parsechain) do
                                self.uci:commit(config)
-                               self.uci:apply(config)
-       
+
                                -- Refresh data because commit changes section names
                                self.uci:load(config)
                        end
                                -- Refresh data because commit changes section names
                                self.uci:load(config)
                        end
-       
+                       if self.apply_on_parse then
+                               self.uci:apply(self.parsechain)
+                       else
+                               self._apply = function()
+                                       local cmd = self.uci:apply(self.parsechain, true)
+                                       return io.popen(cmd)
+                               end
+                       end
+
                        -- Reparse sections
                        -- Reparse sections
-                       Node.parse(self, ...)
-       
+                       Node.parse(self, true)
+
                end
                for i, config in ipairs(self.parsechain) do
                        self.uci:unload(config)
                end
                end
                for i, config in ipairs(self.parsechain) do
                        self.uci:unload(config)
                end
+               if type(self.commit_handler) == "function" then
+                       self:commit_handler(self:submitstate())
+               end
+       end
+
+       if self:submitstate() then
+               if self.save then
+                       return self.changed and FORM_CHANGED or FORM_VALID
+               else
+                       return FORM_INVALID
+               end
+       else
+               return FORM_NODATA
+       end
+end
+
+function Map.render(self, ...)
+       Node.render(self, ...)
+       if self._apply then
+               local fp = self._apply()
+               fp:read("*a")
+               fp:close()
        end
 end
 
        end
 end
 
@@ -336,11 +453,12 @@ function SimpleForm.parse(self, ...)
        end
 
        local state =
        end
 
        local state =
-               not luci.http.formvalue("cbi.submit") and 0
-               or valid and 1
-               or -1
+               not self:submitstate() and FORM_NODATA
+               or valid and FORM_VALID
+               or FORM_INVALID
 
        self.dorender = not self.handle or self:handle(state, self.data) ~= false
 
        self.dorender = not self.handle or self:handle(state, self.data) ~= false
+       return state
 end
 
 function SimpleForm.render(self, ...)
 end
 
 function SimpleForm.render(self, ...)
@@ -349,6 +467,10 @@ function SimpleForm.render(self, ...)
        end
 end
 
        end
 end
 
+function SimpleForm.submitstate(self)
+       return luci.http.formvalue("cbi.submit")
+end
+
 function SimpleForm.section(self, class, ...)
        if instanceof(class, AbstractSection) then
                local obj  = class(self, ...)
 function SimpleForm.section(self, class, ...)
        if instanceof(class, AbstractSection) then
                local obj  = class(self, ...)
@@ -528,7 +650,7 @@ function AbstractSection.create(self, section)
        local stat
 
        if section then
        local stat
 
        if section then
-               stat = self.map:set(section, nil, self.sectiontype)
+               stat = section:match("^%w+$") and self.map:set(section, nil, self.sectiontype)
        else
                section = self.map:add(self.sectiontype)
                stat = section
        else
                section = self.map:add(self.sectiontype)
                stat = section
@@ -585,7 +707,7 @@ end
 
 function Table.parse(self)
        for i, k in ipairs(self:cfgsections()) do
 
 function Table.parse(self)
        for i, k in ipairs(self:cfgsections()) do
-               if luci.http.formvalue("cbi.submit") then
+               if self.map:submitstate() then
                        Node.parse(self, k)
                end
        end
                        Node.parse(self, k)
                end
        end
@@ -611,7 +733,7 @@ NamedSection = class(AbstractSection)
 function NamedSection.__init__(self, map, section, stype, ...)
        AbstractSection.__init__(self, map, stype, ...)
        Node._i18n(self, map.config, section, nil, ...)
 function NamedSection.__init__(self, map, section, stype, ...)
        AbstractSection.__init__(self, map, stype, ...)
        Node._i18n(self, map.config, section, nil, ...)
-       
+
        -- Defaults
        self.addremove = false
 
        -- Defaults
        self.addremove = false
 
@@ -628,7 +750,7 @@ function NamedSection.__init__(self, map, section, stype, ...)
        self.section = section
 end
 
        self.section = section
 end
 
-function NamedSection.parse(self)
+function NamedSection.parse(self, novld)
        local s = self.section
        local active = self:cfgvalue(s)
 
        local s = self.section
        local active = self:cfgvalue(s)
 
@@ -648,10 +770,10 @@ function NamedSection.parse(self)
 
        if active then
                AbstractSection.parse_dynamic(self, s)
 
        if active then
                AbstractSection.parse_dynamic(self, s)
-               if luci.http.formvalue("cbi.submit") then
+               if self.map:submitstate() then
                        Node.parse(self, s)
                        Node.parse(self, s)
-                       
-                       if not self.override_scheme and self.map.scheme then
+
+                       if not novld and not self.override_scheme and self.map.scheme then
                                _uvl_validate_section(self, s)
                        end
                end
                                _uvl_validate_section(self, s)
                        end
                end
@@ -705,7 +827,7 @@ function TypedSection.depends(self, option, value)
        table.insert(self.deps, {option=option, value=value})
 end
 
        table.insert(self.deps, {option=option, value=value})
 end
 
-function TypedSection.parse(self)
+function TypedSection.parse(self, novld)
        if self.addremove then
                -- Remove
                local crval = REMOVE_PREFIX .. self.config
        if self.addremove then
                -- Remove
                local crval = REMOVE_PREFIX .. self.config
@@ -723,10 +845,10 @@ function TypedSection.parse(self)
        local co
        for i, k in ipairs(self:cfgsections()) do
                AbstractSection.parse_dynamic(self, k)
        local co
        for i, k in ipairs(self:cfgsections()) do
                AbstractSection.parse_dynamic(self, k)
-               if luci.http.formvalue("cbi.submit") then
+               if self.map:submitstate() then
                        Node.parse(self, k)
                        Node.parse(self, k)
-                       
-                       if not self.override_scheme and self.map.scheme then
+
+                       if not novld and not self.override_scheme and self.map.scheme then
                                _uvl_validate_section(self, k)
                        end
                end
                                _uvl_validate_section(self, k)
                        end
                end
@@ -757,6 +879,9 @@ function TypedSection.parse(self)
 
                                if name and #name > 0 then
                                        created = self:create(name) and name
 
                                if name and #name > 0 then
                                        created = self:create(name) and name
+                                       if not created then
+                                               self.invalid_cts = true
+                                       end
                                end
                        end
                end
                                end
                        end
                end
@@ -822,22 +947,31 @@ function AbstractValue.__init__(self, map, section, option, ...)
        self.tag_reqerror = {}
        self.tag_error = {}
        self.deps = {}
        self.tag_reqerror = {}
        self.tag_error = {}
        self.deps = {}
-       self.cast = "string"
+       --self.cast = "string"
 
        self.track_missing = false
 
        self.track_missing = false
-       self.rmempty   = false
+       --self.rmempty   = false
        self.default   = nil
        self.size      = nil
        self.optional  = false
        self.default   = nil
        self.size      = nil
        self.optional  = false
+end
 
 
+function AbstractValue.prepare(self)
        -- Use defaults from UVL
        if not self.override_scheme
         and self.map:get_scheme(self.section.sectiontype, self.option) then
                local vs = self.map:get_scheme(self.section.sectiontype, self.option)
        -- Use defaults from UVL
        if not self.override_scheme
         and self.map:get_scheme(self.section.sectiontype, self.option) then
                local vs = self.map:get_scheme(self.section.sectiontype, self.option)
-               self.rmempty     = not vs.required
-               self.cast        = (vs.type == "list") and "list" or "string"
+               if self.rmempty == nil then
+                       self.rmempty = not vs.required
+               end
+               if self.cast == nil then
+                       self.cast = (vs.type == "list") and "list" or "string"
+               end
                self.title       = self.title or vs.title
                self.description = self.description or vs.descr
                self.title       = self.title or vs.title
                self.description = self.description or vs.descr
+               if self.default == nil then
+                       self.default = vs.default
+               end
 
                if vs.depends and not self.override_dependencies then
                        for i, deps in ipairs(vs.depends) do
 
                if vs.depends and not self.override_dependencies then
                        for i, deps in ipairs(vs.depends) do
@@ -848,6 +982,8 @@ function AbstractValue.__init__(self, map, section, option, ...)
                        end
                end
        end
                        end
                end
        end
+
+       self.cast = self.cast or "string"
 end
 
 -- Add a dependencie to another section field
 end
 
 -- Add a dependencie to another section field
@@ -891,7 +1027,7 @@ function AbstractValue.parse(self, section)
        local fvalue = self:formvalue(section)
        local cvalue = self:cfgvalue(section)
 
        local fvalue = self:formvalue(section)
        local cvalue = self:cfgvalue(section)
 
-       if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
+       if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
                fvalue = self:transform(self:validate(fvalue, section))
                if not fvalue then
                        self.tag_invalid[section] = true
                fvalue = self:transform(self:validate(fvalue, section))
                if not fvalue then
                        self.tag_invalid[section] = true
@@ -941,14 +1077,16 @@ end
 -- Return the UCI value of this object
 function AbstractValue.cfgvalue(self, section)
        local value = self.map:get(section, self.option)
 -- Return the UCI value of this object
 function AbstractValue.cfgvalue(self, section)
        local value = self.map:get(section, self.option)
-       if not self.cast or self.cast == type(value) then
+       if not value then
+               return nil
+       elseif not self.cast or self.cast == type(value) then
                return value
        elseif self.cast == "string" then
                if type(value) == "table" then
                        return value[1]
                end
        elseif self.cast == "table" then
                return value
        elseif self.cast == "string" then
                if type(value) == "table" then
                        return value[1]
                end
        elseif self.cast == "table" then
-               return {value}
+               return luci.util.split(value, "%s+", nil, true)
        end
 end
 
        end
 end
 
@@ -1002,6 +1140,20 @@ function DummyValue.__init__(self, ...)
        self.value = nil
 end
 
        self.value = nil
 end
 
+function DummyValue.cfgvalue(self, section)
+       local value
+       if self.value then
+               if type(self.value) == "function" then
+                       value = self:value(section)
+               else
+                       value = self.value
+               end
+       else
+               value = AbstractValue.cfgvalue(self, section)
+       end
+       return value
+end
+
 function DummyValue.parse(self)
 
 end
 function DummyValue.parse(self)
 
 end
@@ -1055,23 +1207,23 @@ function ListValue.__init__(self, ...)
        self.vallist = {}
        self.size   = 1
        self.widget = "select"
        self.vallist = {}
        self.size   = 1
        self.widget = "select"
+end
 
 
+function ListValue.prepare(self, ...)
+       AbstractValue.prepare(self, ...)
        if not self.override_scheme
         and self.map:get_scheme(self.section.sectiontype, self.option) then
                local vs = self.map:get_scheme(self.section.sectiontype, self.option)
        if not self.override_scheme
         and self.map:get_scheme(self.section.sectiontype, self.option) then
                local vs = self.map:get_scheme(self.section.sectiontype, self.option)
-               if self.value and vs.values and not self.override_values then
-                       if self.rmempty or self.optional then
-                               self:value("")
-                       end
-                       for k, v in pairs(vs.values) do
+               if self.value and vs.valuelist and not self.override_values then
+                       for k, v in ipairs(vs.valuelist) do
                                local deps = {}
                                if not self.override_dependencies
                                local deps = {}
                                if not self.override_dependencies
-                                and vs.enum_depends and vs.enum_depends[k] then
-                                       for i, dep in ipairs(vs.enum_depends[k]) do
+                                and vs.enum_depends and vs.enum_depends[v.value] then
+                                       for i, dep in ipairs(vs.enum_depends[v.value]) do
                                                table.insert(deps, _uvl_strip_remote_dependencies(dep))
                                        end
                                end
                                                table.insert(deps, _uvl_strip_remote_dependencies(dep))
                                        end
                                end
-                               self:value(k, v, unpack(deps))
+                               self:value(v.value, v.title or v.value, unpack(deps))
                        end
                end
        end
                        end
                end
        end
@@ -1185,7 +1337,7 @@ function StaticList.validate(self, value)
 
        local valid = {}
        for i, v in ipairs(value) do
 
        local valid = {}
        for i, v in ipairs(value) do
-               if luci.util.contains(self.valuelist, v) then
+               if luci.util.contains(self.vallist, v) then
                        table.insert(valid, v)
                end
        end
                        table.insert(valid, v)
                end
        end
@@ -1209,7 +1361,8 @@ function DynamicList.value(self, key, val)
        table.insert(self.vallist, tostring(val))
 end
 
        table.insert(self.vallist, tostring(val))
 end
 
-function DynamicList.validate(self, value, section)
+function DynamicList.formvalue(self, section)
+       local value = AbstractValue.formvalue(self, section)
        value = (type(value) == "table") and value or {value}
 
        local valid = {}
        value = (type(value) == "table") and value or {value}
 
        local valid = {}
@@ -1247,3 +1400,58 @@ function Button.__init__(self, ...)
        self.inputstyle = nil
        self.rmempty = true
 end
        self.inputstyle = nil
        self.rmempty = true
 end
+
+
+FileUpload = class(AbstractValue)
+
+function FileUpload.__init__(self, ...)
+       AbstractValue.__init__(self, ...)
+       self.template = "cbi/upload"
+       if not self.map.upload_fields then
+               self.map.upload_fields = { self }
+       else
+               self.map.upload_fields[#self.map.upload_fields+1] = self
+       end
+end
+
+function FileUpload.formcreated(self, section)
+       return AbstractValue.formcreated(self, section) or
+               luci.http.formvalue("cbi.rlf."..section.."."..self.option) or
+               luci.http.formvalue("cbi.rlf."..section.."."..self.option..".x")
+end
+
+function FileUpload.cfgvalue(self, section)
+       local val = AbstractValue.cfgvalue(self, section)
+       if val and luci.fs.access(val) then
+               return val
+       end
+       return nil
+end
+
+function FileUpload.formvalue(self, section)
+       local val = AbstractValue.formvalue(self, section)
+       if val then
+               if not luci.http.formvalue("cbi.rlf."..section.."."..self.option) and
+                  not luci.http.formvalue("cbi.rlf."..section.."."..self.option..".x")
+               then
+                       return val
+               end
+               luci.fs.unlink(val)
+               self.value = nil
+       end
+       return nil
+end
+
+function FileUpload.remove(self, section)
+       local val = AbstractValue.formvalue(self, section)
+       if val and luci.fs.access(val) then luci.fs.unlink(val) end
+       return AbstractValue.remove(self, section)
+end
+
+
+FileBrowser = class(AbstractValue)
+
+function FileBrowser.__init__(self, ...)
+       AbstractValue.__init__(self, ...)
+       self.template = "cbi/browser"
+end