X-Git-Url: https://git.archive.openwrt.org/?p=project%2Fluci.git;a=blobdiff_plain;f=libs%2Fcbi%2Fluasrc%2Fcbi.lua;h=78f6b7d5efac6f157dcb16d3061c1e1d7d40c345;hp=e94e1e2fa642ecc22be0858e820ef781f18fe685;hb=14a2e32142cc2bba58de79de8b2b410be21b7ac7;hpb=8f7f03a0c56190b02a8922f01f2c36c62d1dbfe7 diff --git a/libs/cbi/luasrc/cbi.lua b/libs/cbi/luasrc/cbi.lua index e94e1e2fa..78f6b7d5e 100644 --- a/libs/cbi/luasrc/cbi.lua +++ b/libs/cbi/luasrc/cbi.lua @@ -2,7 +2,7 @@ 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: @@ -13,9 +13,9 @@ Copyright 2008 Steven Barth 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, @@ -29,42 +29,175 @@ module("luci.cbi", package.seeall) require("luci.template") require("luci.util") require("luci.http") -require("luci.model.uci") +require("luci.uvl") +require("luci.fs") +--local event = require "luci.sys.event" +local uci = require("luci.model.uci") local class = luci.util.class local instanceof = luci.util.instanceof +FORM_NODATA = 0 +FORM_PROCEED = 0 +FORM_VALID = 1 +FORM_INVALID = -1 +FORM_CHANGED = 2 + +AUTO = true + +CREATE_PREFIX = "cbi.cts." +REMOVE_PREFIX = "cbi.rts." + -- 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") + local i18n = require "luci.i18n" require("luci.config") - require("luci.sys") - - local cbidir = luci.sys.libpath() .. "/model/cbi/" - local func, err = loadfile(cbidir..cbimap..".lua") - - if not func then - return nil - end - + 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") - - 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) - - local map = func() - - if not instanceof(map, Map) then - error("CBI map returns no valid map object!") - return nil + luci.i18n.loadc("uvl") + + local env = { + translate=i18n.translate, + translatef=i18n.translatef, + arg={...} + } + + 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 + 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 - - return map + + 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 + +local function _uvl_validate_section(node, name) + local co = node.map:get() + + luci.uvl.STRICT_UNKNOWN_OPTIONS = false + luci.uvl.STRICT_UNKNOWN_SECTIONS = false + + 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 + + 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 + + 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) + local clean = {} + + for k, v in pairs(deps) do + k = k:gsub("%$config%.%$section%.", "") + if k:match("^[%w_]+$") and type(v) == "string" then + clean[k] = v + end + end + + return clean end + -- Node pseudo abstract class Node = class() @@ -75,6 +208,29 @@ function Node.__init__(self, title, description) 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 and config:gsub("[^%w]+", "") or "" + + if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end + if option then key = key .. "_" .. tostring(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 + +-- 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) @@ -113,28 +269,112 @@ function Template.__init__(self, template) self.template = template end +function Template.render(self) + luci.template.render(self.template, {self=self}) +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, ...) + Node._i18n(self, config, nil, nil, ...) + self.config = config + self.parsechain = {self.config} 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 + self.apply_on_parse = nil + self.uci = uci.cursor() + self.save = true + + self.changed = false + + if not self.uci:load(self.config) then error("Unable to read UCI data: " .. self.config) end + + self.validator = luci.uvl.UVL() + self.scheme = self.validator:get_scheme(self.config) + +end + +function Map.get_scheme(self, sectiontype, option) + if not option then + return self.scheme and self.scheme.sections[sectiontype] + else + return self.scheme and self.scheme.variables[sectiontype] + and self.scheme.variables[sectiontype][option] + end +end + +function Map.submitstate(self) + return luci.http.formvalue("cbi.submit") +end + +-- Chain foreign config +function Map.chain(self, config) + table.insert(self.parsechain, config) end -- Use optimized UCI writing -function Map.parse(self, ...) - self.uci:t_load(self.config) - Node.parse(self, ...) - self.uci:t_save(self.config) +function Map.parse(self) + Node.parse(self) + + if self.save then + for i, config in ipairs(self.parsechain) do + self.uci:save(config) + end + if self:submitstate() and (self.autoapply or luci.http.formvalue("cbi.apply")) then + for i, config in ipairs(self.parsechain) do + self.uci:commit(config) + + -- 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 + Node.parse(self, true) + + 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 + self.state = self.changed and FORM_CHANGED or FORM_VALID + else + self.state = FORM_INVALID + end + else + self.state = FORM_NODATA + end + + return self.state +end + +function Map.render(self, ...) + Node.render(self, ...) + if self._apply then + local fp = self._apply() + fp:read("*a") + fp:close() + end end -- Creates a child section @@ -150,62 +390,148 @@ end -- 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 self.uci:add(self.config, sectiontype) 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 self.uci:set(self.config, section, option, value) + else + return self.uci:set(self.config, section, value) end - return stat 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 self.uci:delete(self.config, section, option) + else + return self.uci:delete(self.config, section) end - return stat end --- UCI get (cached) +-- UCI get 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 self.uci:get_all(self.config) + elseif option then + return self.uci:get(self.config, section, option) + else + return self.uci:get_all(self.config, section) + end +end + + +--[[ +Page - A simple node +]]-- + +Page = class(Node) +Page.__init__ = Node.__init__ +Page.parse = function() end + + +--[[ +SimpleForm - A Simple non-UCI form +]]-- +SimpleForm = class(Node) + +function SimpleForm.__init__(self, config, title, description, data) + Node.__init__(self, title, description) + self.config = config + self.data = data or {} + self.template = "cbi/simpleform" + self.dorender = true + self.pageaction = false +end + +function SimpleForm.parse(self, ...) + if luci.http.formvalue("cbi.submit") then + Node.parse(self, 1, ...) + end + + local valid = true + for k, j in ipairs(self.children) do + for i, v in ipairs(j.children) do + valid = valid + and (not v.tag_missing or not v.tag_missing[1]) + and (not v.tag_invalid or not v.tag_invalid[1]) + end + end + + local state = + 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 + return state +end + +function SimpleForm.render(self, ...) + if self.dorender then + Node.render(self, ...) + 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, ...) + self:append(obj) + return obj + else + error("class must be a descendent of AbstractSection") + end +end + +-- Creates a child field +function SimpleForm.field(self, class, ...) + local section + for k, v in ipairs(self.children) do + if instanceof(v, SimpleSection) then + section = v + break + end + end + if not section then + section = self:section(SimpleSection) + end + + if instanceof(class, AbstractValue) then + local obj = class(self, section, ...) + obj.track_missing = true + section:append(obj) + return obj else - return self.ucidata[section] + error("class must be a descendent of AbstractValue") end end +function SimpleForm.set(self, section, option, value) + self.data[option] = value +end + + +function SimpleForm.del(self, section, option) + self.data[option] = nil +end + + +function SimpleForm.get(self, section, option) + return self.data[option] +end + + +function SimpleForm.get_scheme() + return nil +end + + --[[ AbstractSection @@ -218,21 +544,47 @@ function AbstractSection.__init__(self, map, sectiontype, ...) self.map = map self.config = map.config self.optionals = {} - + self.defaults = {} + self.fields = {} + self.tag_error = {} + self.tag_invalid = {} + self.tag_deperror = {} + self.changed = false + 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, ...) + -- Autodetect from UVL + if class == true and self.map:get_scheme(self.sectiontype, option) then + local vs = self.map:get_scheme(self.sectiontype, option) + if vs.type == "boolean" then + class = Flag + elseif vs.type == "list" then + class = DynamicList + elseif vs.type == "enum" or vs.type == "reference" then + class = ListValue + else + class = Value + end + end + if instanceof(class, AbstractValue) then - local obj = class(self.map, ...) + local obj = class(self.map, self, option, ...) + + Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...) + self:append(obj) + self.fields[option] = obj return obj + elseif class == true then + error("No valid class was given and autodetection failed.") else - error("class must be a descendent of AbstractValue") - end + error("class must be a descendant of AbstractValue") + end end -- Parse optional options @@ -240,9 +592,9 @@ function AbstractSection.parse_optionals(self, section) if not self.optional then return end - + 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 @@ -253,7 +605,7 @@ function AbstractSection.parse_optionals(self, section) end end end - + if field and #field > 0 and self.dynamic then self:add_dynamic(field) end @@ -270,33 +622,39 @@ function AbstractSection.parse_dynamic(self, section) 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 - + 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 - + 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) return self.map:get(section) end +-- Push events +function AbstractSection.push_events(self) + --luci.util.append(self.map.events, self.events) + self.map.changed = true +end + -- Removes the section function AbstractSection.remove(self, section) return self.map:del(section) @@ -304,7 +662,84 @@ end -- Creates the section function AbstractSection.create(self, section) - return self.map:set(section, nil, self.sectiontype) + local stat + + if section then + stat = section:match("^%w+$") and 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 + + +SimpleSection = class(AbstractSection) + +function SimpleSection.__init__(self, form, ...) + AbstractSection.__init__(self, form, nil, ...) + self.template = "cbi/nullsection" +end + + +Table = class(AbstractSection) + +function Table.__init__(self, form, data, ...) + local datasource = {} + datasource.config = "table" + self.data = data + + function datasource.get(self, section, option) + return data[section] and data[section][option] + end + + function datasource.submitstate(self) + return luci.http.formvalue("cbi.submit") + end + + function datasource.del(...) + return true + end + + function datasource.get_scheme() + return nil + end + + AbstractSection.__init__(self, datasource, "table", ...) + self.template = "cbi/tblsection" + self.rowcolors = true + self.anonymous = true +end + +function Table.parse(self) + for i, k in ipairs(self:cfgsections()) do + if self.map:submitstate() then + Node.parse(self, k) + end + end +end + +function Table.cfgsections(self) + local sections = {} + + for i, v in luci.util.kspairs(self.data) do + table.insert(sections, i) + end + + return sections end @@ -314,19 +749,30 @@ NamedSection - A fixed configuration section defined by its name ]]-- NamedSection = class(AbstractSection) -function NamedSection.__init__(self, map, section, ...) - AbstractSection.__init__(self, map, ...) +function NamedSection.__init__(self, map, section, stype, ...) + AbstractSection.__init__(self, map, stype, ...) + Node._i18n(self, map.config, section, nil, ...) + + -- Defaults + self.addremove = false + + -- Use defaults from UVL + if not self.override_scheme and self.map:get_scheme(self.sectiontype) then + local vs = self.map:get_scheme(self.sectiontype) + self.addremove = not vs.unique and not vs.required + self.dynamic = vs.dynamic + self.title = self.title or vs.title + self.description = self.description or vs.descr + end + self.template = "cbi/nsection" - self.section = section - self.addremove = false end -function NamedSection.parse(self) - local s = self.section +function NamedSection.parse(self, novld) + local s = self.section local active = self:cfgvalue(s) - - + if self.addremove then local path = self.config.."."..s if active then -- Remove the section @@ -334,21 +780,28 @@ function NamedSection.parse(self) 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 - + if active then AbstractSection.parse_dynamic(self, s) - if luci.http.formvalue("cbi.submit") then + if self.map:submitstate() then Node.parse(self, s) + + if not novld and not self.override_scheme and self.map.scheme then + _uvl_validate_section(self, s) + end end AbstractSection.parse_optionals(self, s) - end + + if self.changed then + self:push_events() + end + end end @@ -356,48 +809,40 @@ 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 - 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) -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.excludes = {} - self.anonymous = false + + -- Use defaults from UVL + if not self.override_scheme and self.map:get_scheme(self.sectiontype) then + local vs = self.map:get_scheme(self.sectiontype) + self.addremove = not vs.unique and not vs.required + self.dynamic = vs.dynamic + self.anonymous = not vs.named + self.title = self.title or vs.title + self.description = self.description or vs.descr + end 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) + self.map.uci:foreach(self.map.config, self.sectiontype, + function (section) + if self:checkscope(section[".name"]) then + table.insert(sections, section[".name"]) 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 @@ -405,80 +850,97 @@ function TypedSection.depends(self, option, value) 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, novld) + if self.addremove then + -- Remove + local crval = REMOVE_PREFIX .. self.config + local name = luci.http.formvaluetable(crval) + for k,v in pairs(name) do + if k:sub(-2) == ".x" then + k = k:sub(1, #k - 2) + end + if self:cfgvalue(k) and self:checkscope(k) then + self:remove(k) + end + end + end + + local co + for i, k in ipairs(self:cfgsections()) do + AbstractSection.parse_dynamic(self, k) + if self.map:submitstate() then + Node.parse(self, k) + + if not novld and not self.override_scheme and self.map.scheme then + _uvl_validate_section(self, k) + end + end + AbstractSection.parse_optionals(self, k) + end -function TypedSection.parse(self) if self.addremove then -- Create - local crval = "cbi.cts." .. self.config .. "." .. self.sectiontype + local created + local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype local name = luci.http.formvalue(crval) if self.anonymous then if name then - self:create() + created = self:create() end - else + else if name then -- Ignore if it already exists if self:cfgvalue(name) then name = nil; end - + name = self:checkscope(name) - + if not name then self.err_invalid = true - end - - if name and name:len() > 0 then - self:create(name) + end + + if name and #name > 0 then + created = self:create(name) and name + if not created then + self.invalid_cts = true + end end end end - - -- Remove - crval = "cbi.rts." .. self.config - name = luci.http.formvaluetable(crval) - for k,v in pairs(name) do - if self:cfgvalue(k) and self:checkscope(k) then - self:remove(k) - end - end - end - - for i, k in ipairs(self:cfgsections()) do - AbstractSection.parse_dynamic(self, k) - if luci.http.formvalue("cbi.submit") then - Node.parse(self, k) + + if created then + AbstractSection.parse_optionals(self, created) end - AbstractSection.parse_optionals(self, k) + end + + if created or self.changed then + self:push_events() end end -- 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 - + -- 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 - + if not stat then return nil end end - + return self:validate(section) end @@ -492,7 +954,7 @@ end --[[ 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 @@ -501,23 +963,72 @@ AbstractValue - An abstract Value Type ]]-- AbstractValue = class(Node) -function AbstractValue.__init__(self, map, option, ...) +function AbstractValue.__init__(self, map, section, option, ...) Node.__init__(self, ...) - self.option = option - self.map = map - self.config = map.config + self.section = section + self.option = option + self.map = map + self.config = map.config self.tag_invalid = {} + self.tag_missing = {} + self.tag_reqerror = {} + self.tag_error = {} self.deps = {} - - self.rmempty = false - self.default = nil - self.size = nil - self.optional = false + --self.cast = "string" + + self.track_missing = false + --self.rmempty = 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) + 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 + 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 + deps = _uvl_strip_remote_dependencies(deps) + if next(deps) then + self:depends(deps) + end + end + end + end + + self.cast = self.cast or "string" end -- Add a dependencie to another section field function AbstractValue.depends(self, field, value) - table.insert(self.deps, {field=field, value=value}) + local deps + if type(field) == "string" then + deps = {} + deps[field] = value + else + deps = field + end + + table.insert(self.deps, {deps=deps, add=""}) +end + +-- Generates the unique CBID +function AbstractValue.cbid(self, section) + return "cbid."..self.map.config.."."..section.."."..self.option end -- Return whether this object should be created @@ -528,24 +1039,42 @@ end -- Returns the formvalue for this object function AbstractValue.formvalue(self, section) - local key = "cbid."..self.map.config.."."..section.."."..self.option - return luci.http.formvalue(key) + return luci.http.formvalue(self:cbid(section)) +end + +function AbstractValue.additional(self, value) + self.optional = value +end + +function AbstractValue.mandatory(self, value) + self.rmempty = not value end 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) + local cvalue = self:cfgvalue(section) + + 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 end - if fvalue and not (fvalue == self:cfgvalue(section)) then - self:write(section, fvalue) - end + if fvalue and not (fvalue == cvalue) then + if self:write(section, fvalue) then + -- Push events + self.section.changed = true + --luci.util.append(self.map.events, self.events) + end + end else -- Unset the UCI or error if self.rmempty or self.optional then - self:remove(section) + if self:remove(section) then + -- Push events + self.section.changed = true + --luci.util.append(self.map.events, self.events) + end + elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then + self.tag_missing[section] = true end end end @@ -554,14 +1083,46 @@ end 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 + scope.section = s + scope.cbid = self:cbid(s) + scope.striptags = luci.util.striptags + + scope.ifattr = function(cond,key,val) + if cond then + return string.format( + ' %s="%s"', tostring(key), + luci.util.pcdata(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 -- Return the UCI value of this object function AbstractValue.cfgvalue(self, section) - return self.map:get(section, self.option) + local value = self.map:get(section, self.option) + 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 luci.util.split(value, "%s+", nil, true) + end end -- Validate the form value @@ -569,6 +1130,9 @@ function AbstractValue.validate(self, value) return value end +AbstractValue.transform = AbstractValue.validate + + -- Write to UCI function AbstractValue.write(self, section, value) return self.map:set(section, self.option, value) @@ -585,46 +1149,48 @@ end --[[ 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" - - self.maxlength = nil - self.isnumber = false - self.isinteger = false + self.keylist = {} + self.vallist = {} 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 -- DummyValue - This does nothing except being there DummyValue = class(AbstractValue) -function DummyValue.__init__(self, map, ...) - AbstractValue.__init__(self, map, ...) +function DummyValue.__init__(self, ...) + AbstractValue.__init__(self, ...) self.template = "cbi/dvalue" self.value = nil end -function DummyValue.parse(self) - +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.render(self, s) - luci.template.render(self.template, {self=self, section=s}) +function DummyValue.parse(self) + end @@ -636,7 +1202,7 @@ Flag = class(AbstractValue) function Flag.__init__(self, ...) AbstractValue.__init__(self, ...) self.template = "cbi/fvalue" - + self.enabled = "1" self.disabled = "0" end @@ -644,17 +1210,17 @@ end -- 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 - 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) - end + end else self:remove(section) end @@ -671,17 +1237,45 @@ ListValue = class(AbstractValue) function ListValue.__init__(self, ...) AbstractValue.__init__(self, ...) self.template = "cbi/lvalue" + self.keylist = {} self.vallist = {} - self.size = 1 self.widget = "select" end -function ListValue.value(self, key, val) +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 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 + 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 + self:value(v.value, v.title or v.value, unpack(deps)) + end + end + end +end + +function ListValue.value(self, key, val, ...) + if luci.util.contains(self.keylist, key) then + return + end + val = val or key table.insert(self.keylist, tostring(key)) - table.insert(self.vallist, tostring(val)) + table.insert(self.vallist, tostring(val)) + + for i, deps in ipairs({...}) do + table.insert(self.deps, {add = "-"..key, deps=deps}) + end end function ListValue.validate(self, val) @@ -704,45 +1298,195 @@ MultiValue = class(AbstractValue) function MultiValue.__init__(self, ...) AbstractValue.__init__(self, ...) self.template = "cbi/mvalue" + self.keylist = {} - self.vallist = {} - + self.vallist = {} + 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) + if luci.util.contains(self.keylist, key) then + return + end + 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) - + if not(type(val) == "string") then return {} end - + 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 - result = result .. self.delimiter .. value - end + result = result and (result .. self.delimiter .. value) or value + end end - - if result:len() > 0 then - return result:sub(self.delimiter:len() + 1) + + return result +end + + +StaticList = class(MultiValue) + +function StaticList.__init__(self, ...) + MultiValue.__init__(self, ...) + self.cast = "table" + self.valuelist = self.cfgvalue + + 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 + for k, v in pairs(vs.values) do + self:value(k, v) + end + end + end +end + +function StaticList.validate(self, value) + value = (type(value) == "table") and value or {value} + + local valid = {} + for i, v in ipairs(value) do + if luci.util.contains(self.vallist, v) then + table.insert(valid, v) + end + end + return valid +end + + +DynamicList = class(AbstractValue) + +function DynamicList.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/dynlist" + self.cast = "table" + self.keylist = {} + self.vallist = {} +end + +function DynamicList.value(self, key, val) + val = val or key + table.insert(self.keylist, tostring(key)) + table.insert(self.vallist, tostring(val)) +end + +function DynamicList.formvalue(self, section) + local value = AbstractValue.formvalue(self, section) + value = (type(value) == "table") and value or {value} + + local valid = {} + for i, v in ipairs(value) do + if v and #v > 0 + and not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i) + and not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then + table.insert(valid, v) + end + end + + return valid +end + + +--[[ +TextValue - A multi-line value + rows: Rows +]]-- +TextValue = class(AbstractValue) + +function TextValue.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/tvalue" +end + +--[[ +Button +]]-- +Button = class(AbstractValue) + +function Button.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/button" + 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 - return nil + self.map.upload_fields[#self.map.upload_fields+1] = self end -end \ No newline at end of file +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