* luci/libs/cbi: improved error integration with uvl
[project/luci.git] / libs / cbi / luasrc / cbi.lua
1 --[[
2 LuCI - Configuration Bind Interface
3
4 Description:
5 Offers an interface for binding configuration values to certain
6 data types. Supports value and range validation and basic dependencies.
7
8 FileId:
9 $Id$
10
11 License:
12 Copyright 2008 Steven Barth <steven@midlink.org>
13
14 Licensed under the Apache License, Version 2.0 (the "License");
15 you may not use this file except in compliance with the License.
16 You may obtain a copy of the License at
17
18 http://www.apache.org/licenses/LICENSE-2.0
19
20 Unless required by applicable law or agreed to in writing, software
21 distributed under the License is distributed on an "AS IS" BASIS,
22 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 See the License for the specific language governing permissions and
24 limitations under the License.
25
26 ]]--
27 module("luci.cbi", package.seeall)
28
29 require("luci.template")
30 require("luci.util")
31 require("luci.http")
32 require("luci.uvl")
33
34 local uci = require("luci.model.uci")
35 local class = luci.util.class
36 local instanceof = luci.util.instanceof
37
38 FORM_NODATA = 0
39 FORM_VALID = 1
40 FORM_INVALID = -1
41
42 AUTO = true
43
44 CREATE_PREFIX = "cbi.cts."
45 REMOVE_PREFIX = "cbi.rts."
46
47 -- Loads a CBI map from given file, creating an environment and returns it
48 function load(cbimap, ...)
49 require("luci.fs")
50 local i18n = require "luci.i18n"
51 require("luci.config")
52 require("luci.util")
53
54 local cbidir = luci.util.libpath() .. "/model/cbi/"
55 local func, err = loadfile(cbimap) or loadfile(cbidir..cbimap..".lua")
56 assert(func, err)
57
58 luci.i18n.loadc("cbi")
59 luci.i18n.loadc("uvl")
60
61 local env = {
62 translate=i18n.translate,
63 translatef=i18n.translatef,
64 arg={...}
65 }
66
67 setfenv(func, setmetatable(env, {__index =
68 function(tbl, key)
69 return rawget(tbl, key) or _M[key] or _G[key]
70 end}))
71
72 local maps = {func()}
73
74 for i, map in ipairs(maps) do
75 if not instanceof(map, Node) then
76 error("CBI map returns no valid map object!")
77 return nil
78 end
79 end
80
81 return maps
82 end
83
84 local function _uvl_validate_section(node, name)
85 local co = node.map:get()
86
87 luci.uvl.STRICT_UNKNOWN_OPTIONS = false
88 luci.uvl.STRICT_UNKNOWN_SECTIONS = false
89
90 local function tag_fields(e)
91 if e.option and node.fields[e.option] then
92 node.fields[e.option].error = e
93 elseif e.childs then
94 for _, c in ipairs(e.childs) do tag_fields(c) end
95 end
96 end
97
98 local function tag_section(e)
99 local s = { }
100 for _, c in ipairs(e.childs) do
101 if c.childs and not c:is(luci.uvl.errors.ERR_DEPENDENCY) then
102 table.insert( s, c.childs[1]:string() )
103 else
104 table.insert( s, c:string() )
105 end
106 end
107 if #s > 0 then node.error = s end
108 end
109
110 local stat, err = node.map.validator:validate_section(node.config, name, co)
111 if err then
112 node.map.save = false
113
114 tag_fields(err)
115 tag_section(err)
116 --[[
117 if err:is(luci.uvl.errors.ERR_DEPENDENCY) then
118 node.tag_deperror[name] = err:string()
119 else
120 node.tag_invalid[name] = err:string()
121 end
122 for i, v in ipairs(err.childs) do
123 if v.option and node.fields[v.option] then
124 if v:is(luci.uvl.errors.ERR_DEPENDENCY) then
125 node.fields[v.option].tag_deperror[name] = v:string()
126 elseif v:is(luci.uvl.errors.ERR_OPT_REQUIRED) then
127 node.fields[v.option].tag_missing[name] = v:string()
128 node.tag_reqerror[name] = v:string()
129 elseif v:is(luci.uvl.errors.ERR_OPTION) then
130 node.fields[v.option].tag_invalid[name] = v:string()
131 end
132 end
133 end
134 ]]
135 end
136
137 end
138
139 local function _uvl_strip_remote_dependencies(deps)
140 local clean = {}
141
142 for k, v in pairs(deps) do
143 k = k:gsub("%$config%.%$section%.", "")
144 if k:match("^[%w_]+$") and type(v) == "string" then
145 clean[k] = v
146 end
147 end
148
149 return clean
150 end
151
152
153 -- Node pseudo abstract class
154 Node = class()
155
156 function Node.__init__(self, title, description)
157 self.children = {}
158 self.title = title or ""
159 self.description = description or ""
160 self.template = "cbi/node"
161 end
162
163 -- i18n helper
164 function Node._i18n(self, config, section, option, title, description)
165
166 -- i18n loaded?
167 if type(luci.i18n) == "table" then
168
169 local key = config and config:gsub("[^%w]+", "") or ""
170
171 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
172 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
173
174 self.title = title or luci.i18n.translate( key, option or section or config )
175 self.description = description or luci.i18n.translate( key .. "_desc", "" )
176 end
177 end
178
179 -- Append child nodes
180 function Node.append(self, obj)
181 table.insert(self.children, obj)
182 end
183
184 -- Parse this node and its children
185 function Node.parse(self, ...)
186 for k, child in ipairs(self.children) do
187 child:parse(...)
188 end
189 end
190
191 -- Render this node
192 function Node.render(self, scope)
193 scope = scope or {}
194 scope.self = self
195
196 luci.template.render(self.template, scope)
197 end
198
199 -- Render the children
200 function Node.render_children(self, ...)
201 for k, node in ipairs(self.children) do
202 node:render(...)
203 end
204 end
205
206
207 --[[
208 A simple template element
209 ]]--
210 Template = class(Node)
211
212 function Template.__init__(self, template)
213 Node.__init__(self)
214 self.template = template
215 end
216
217 function Template.render(self)
218 luci.template.render(self.template, {self=self})
219 end
220
221
222 --[[
223 Map - A map describing a configuration file
224 ]]--
225 Map = class(Node)
226
227 function Map.__init__(self, config, ...)
228 Node.__init__(self, ...)
229 Node._i18n(self, config, nil, nil, ...)
230
231 self.config = config
232 self.parsechain = {self.config}
233 self.template = "cbi/map"
234 self.uci = uci.cursor()
235 self.save = true
236 if not self.uci:load(self.config) then
237 error("Unable to read UCI data: " .. self.config)
238 end
239
240 self.validator = luci.uvl.UVL()
241 self.scheme = self.validator:get_scheme(self.config)
242
243 end
244
245 function Map.get_scheme(self, sectiontype, option)
246 if not option then
247 return self.scheme and self.scheme.sections[sectiontype]
248 else
249 return self.scheme and self.scheme.variables[sectiontype]
250 and self.scheme.variables[sectiontype][option]
251 end
252 end
253
254
255 -- Chain foreign config
256 function Map.chain(self, config)
257 table.insert(self.parsechain, config)
258 end
259
260 -- Use optimized UCI writing
261 function Map.parse(self, ...)
262 Node.parse(self, ...)
263
264 if self.save then
265 for i, config in ipairs(self.parsechain) do
266 self.uci:save(config)
267 end
268 if luci.http.formvalue("cbi.apply") then
269 for i, config in ipairs(self.parsechain) do
270 self.uci:commit(config)
271 self.uci:apply(config)
272
273 -- Refresh data because commit changes section names
274 self.uci:load(config)
275 end
276
277 -- Reparse sections
278 Node.parse(self, ...)
279
280 end
281 for i, config in ipairs(self.parsechain) do
282 self.uci:unload(config)
283 end
284 end
285 end
286
287 -- Creates a child section
288 function Map.section(self, class, ...)
289 if instanceof(class, AbstractSection) then
290 local obj = class(self, ...)
291 self:append(obj)
292 return obj
293 else
294 error("class must be a descendent of AbstractSection")
295 end
296 end
297
298 -- UCI add
299 function Map.add(self, sectiontype)
300 return self.uci:add(self.config, sectiontype)
301 end
302
303 -- UCI set
304 function Map.set(self, section, option, value)
305 if option then
306 return self.uci:set(self.config, section, option, value)
307 else
308 return self.uci:set(self.config, section, value)
309 end
310 end
311
312 -- UCI del
313 function Map.del(self, section, option)
314 if option then
315 return self.uci:delete(self.config, section, option)
316 else
317 return self.uci:delete(self.config, section)
318 end
319 end
320
321 -- UCI get
322 function Map.get(self, section, option)
323 if not section then
324 return self.uci:get_all(self.config)
325 elseif option then
326 return self.uci:get(self.config, section, option)
327 else
328 return self.uci:get_all(self.config, section)
329 end
330 end
331
332
333 --[[
334 Page - A simple node
335 ]]--
336
337 Page = class(Node)
338 Page.__init__ = Node.__init__
339 Page.parse = function() end
340
341
342 --[[
343 SimpleForm - A Simple non-UCI form
344 ]]--
345 SimpleForm = class(Node)
346
347 function SimpleForm.__init__(self, config, title, description, data)
348 Node.__init__(self, title, description)
349 self.config = config
350 self.data = data or {}
351 self.template = "cbi/simpleform"
352 self.dorender = true
353 end
354
355 function SimpleForm.parse(self, ...)
356 if luci.http.formvalue("cbi.submit") then
357 Node.parse(self, 1, ...)
358 end
359
360 local valid = true
361 for k, j in ipairs(self.children) do
362 for i, v in ipairs(j.children) do
363 valid = valid
364 and (not v.tag_missing or not v.tag_missing[1])
365 and (not v.tag_invalid or not v.tag_invalid[1])
366 end
367 end
368
369 local state =
370 not luci.http.formvalue("cbi.submit") and 0
371 or valid and 1
372 or -1
373
374 self.dorender = not self.handle or self:handle(state, self.data) ~= false
375 end
376
377 function SimpleForm.render(self, ...)
378 if self.dorender then
379 Node.render(self, ...)
380 end
381 end
382
383 function SimpleForm.section(self, class, ...)
384 if instanceof(class, AbstractSection) then
385 local obj = class(self, ...)
386 self:append(obj)
387 return obj
388 else
389 error("class must be a descendent of AbstractSection")
390 end
391 end
392
393 -- Creates a child field
394 function SimpleForm.field(self, class, ...)
395 local section
396 for k, v in ipairs(self.children) do
397 if instanceof(v, SimpleSection) then
398 section = v
399 break
400 end
401 end
402 if not section then
403 section = self:section(SimpleSection)
404 end
405
406 if instanceof(class, AbstractValue) then
407 local obj = class(self, section, ...)
408 obj.track_missing = true
409 section:append(obj)
410 return obj
411 else
412 error("class must be a descendent of AbstractValue")
413 end
414 end
415
416 function SimpleForm.set(self, section, option, value)
417 self.data[option] = value
418 end
419
420
421 function SimpleForm.del(self, section, option)
422 self.data[option] = nil
423 end
424
425
426 function SimpleForm.get(self, section, option)
427 return self.data[option]
428 end
429
430
431 function SimpleForm.get_scheme()
432 return nil
433 end
434
435
436
437 --[[
438 AbstractSection
439 ]]--
440 AbstractSection = class(Node)
441
442 function AbstractSection.__init__(self, map, sectiontype, ...)
443 Node.__init__(self, ...)
444 self.sectiontype = sectiontype
445 self.map = map
446 self.config = map.config
447 self.optionals = {}
448 self.defaults = {}
449 self.fields = {}
450 self.tag_error = {}
451 self.tag_invalid = {}
452 self.tag_deperror = {}
453
454 self.optional = true
455 self.addremove = false
456 self.dynamic = false
457 end
458
459 -- Appends a new option
460 function AbstractSection.option(self, class, option, ...)
461 -- Autodetect from UVL
462 if class == true and self.map:get_scheme(self.sectiontype, option) then
463 local vs = self.map:get_scheme(self.sectiontype, option)
464 if vs.type == "boolean" then
465 class = Flag
466 elseif vs.type == "list" then
467 class = DynamicList
468 elseif vs.type == "enum" or vs.type == "reference" then
469 class = ListValue
470 else
471 class = Value
472 end
473 end
474
475 if instanceof(class, AbstractValue) then
476 local obj = class(self.map, self, option, ...)
477
478 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
479
480 self:append(obj)
481 self.fields[option] = obj
482 return obj
483 elseif class == true then
484 error("No valid class was given and autodetection failed.")
485 else
486 error("class must be a descendant of AbstractValue")
487 end
488 end
489
490 -- Parse optional options
491 function AbstractSection.parse_optionals(self, section)
492 if not self.optional then
493 return
494 end
495
496 self.optionals[section] = {}
497
498 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
499 for k,v in ipairs(self.children) do
500 if v.optional and not v:cfgvalue(section) then
501 if field == v.option then
502 field = nil
503 else
504 table.insert(self.optionals[section], v)
505 end
506 end
507 end
508
509 if field and #field > 0 and self.dynamic then
510 self:add_dynamic(field)
511 end
512 end
513
514 -- Add a dynamic option
515 function AbstractSection.add_dynamic(self, field, optional)
516 local o = self:option(Value, field, field)
517 o.optional = optional
518 end
519
520 -- Parse all dynamic options
521 function AbstractSection.parse_dynamic(self, section)
522 if not self.dynamic then
523 return
524 end
525
526 local arr = luci.util.clone(self:cfgvalue(section))
527 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
528 for k, v in pairs(form) do
529 arr[k] = v
530 end
531
532 for key,val in pairs(arr) do
533 local create = true
534
535 for i,c in ipairs(self.children) do
536 if c.option == key then
537 create = false
538 end
539 end
540
541 if create and key:sub(1, 1) ~= "." then
542 self:add_dynamic(key, true)
543 end
544 end
545 end
546
547 -- Returns the section's UCI table
548 function AbstractSection.cfgvalue(self, section)
549 return self.map:get(section)
550 end
551
552 -- Removes the section
553 function AbstractSection.remove(self, section)
554 return self.map:del(section)
555 end
556
557 -- Creates the section
558 function AbstractSection.create(self, section)
559 local stat
560
561 if section then
562 stat = self.map:set(section, nil, self.sectiontype)
563 else
564 section = self.map:add(self.sectiontype)
565 stat = section
566 end
567
568 if stat then
569 for k,v in pairs(self.children) do
570 if v.default then
571 self.map:set(section, v.option, v.default)
572 end
573 end
574
575 for k,v in pairs(self.defaults) do
576 self.map:set(section, k, v)
577 end
578 end
579
580 return stat
581 end
582
583
584 SimpleSection = class(AbstractSection)
585
586 function SimpleSection.__init__(self, form, ...)
587 AbstractSection.__init__(self, form, nil, ...)
588 self.template = "cbi/nullsection"
589 end
590
591
592 Table = class(AbstractSection)
593
594 function Table.__init__(self, form, data, ...)
595 local datasource = {}
596 datasource.config = "table"
597 self.data = data
598
599 function datasource.get(self, section, option)
600 return data[section] and data[section][option]
601 end
602
603 function datasource.del(...)
604 return true
605 end
606
607 function datasource.get_scheme()
608 return nil
609 end
610
611 AbstractSection.__init__(self, datasource, "table", ...)
612 self.template = "cbi/tblsection"
613 self.rowcolors = true
614 self.anonymous = true
615 end
616
617 function Table.parse(self)
618 for i, k in ipairs(self:cfgsections()) do
619 if luci.http.formvalue("cbi.submit") then
620 Node.parse(self, k)
621 end
622 end
623 end
624
625 function Table.cfgsections(self)
626 local sections = {}
627
628 for i, v in luci.util.kspairs(self.data) do
629 table.insert(sections, i)
630 end
631
632 return sections
633 end
634
635
636
637 --[[
638 NamedSection - A fixed configuration section defined by its name
639 ]]--
640 NamedSection = class(AbstractSection)
641
642 function NamedSection.__init__(self, map, section, stype, ...)
643 AbstractSection.__init__(self, map, stype, ...)
644 Node._i18n(self, map.config, section, nil, ...)
645
646 -- Defaults
647 self.addremove = false
648
649 -- Use defaults from UVL
650 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
651 local vs = self.map:get_scheme(self.sectiontype)
652 self.addremove = not vs.unique and not vs.required
653 self.dynamic = vs.dynamic
654 self.title = self.title or vs.title
655 self.description = self.description or vs.descr
656 end
657
658 self.template = "cbi/nsection"
659 self.section = section
660 end
661
662 function NamedSection.parse(self)
663 local s = self.section
664 local active = self:cfgvalue(s)
665
666 if self.addremove then
667 local path = self.config.."."..s
668 if active then -- Remove the section
669 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
670 return
671 end
672 else -- Create and apply default values
673 if luci.http.formvalue("cbi.cns."..path) then
674 self:create(s)
675 return
676 end
677 end
678 end
679
680 if active then
681 AbstractSection.parse_dynamic(self, s)
682 if luci.http.formvalue("cbi.submit") then
683 Node.parse(self, s)
684
685 if not self.override_scheme and self.map.scheme then
686 _uvl_validate_section(self, s)
687 end
688 end
689 AbstractSection.parse_optionals(self, s)
690 end
691 end
692
693
694 --[[
695 TypedSection - A (set of) configuration section(s) defined by the type
696 addremove: Defines whether the user can add/remove sections of this type
697 anonymous: Allow creating anonymous sections
698 validate: a validation function returning nil if the section is invalid
699 ]]--
700 TypedSection = class(AbstractSection)
701
702 function TypedSection.__init__(self, map, type, ...)
703 AbstractSection.__init__(self, map, type, ...)
704 Node._i18n(self, map.config, type, nil, ...)
705
706 self.template = "cbi/tsection"
707 self.deps = {}
708 self.anonymous = false
709
710 -- Use defaults from UVL
711 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
712 local vs = self.map:get_scheme(self.sectiontype)
713 self.addremove = not vs.unique and not vs.required
714 self.dynamic = vs.dynamic
715 self.anonymous = not vs.named
716 self.title = self.title or vs.title
717 self.description = self.description or vs.descr
718 end
719 end
720
721 -- Return all matching UCI sections for this TypedSection
722 function TypedSection.cfgsections(self)
723 local sections = {}
724 self.map.uci:foreach(self.map.config, self.sectiontype,
725 function (section)
726 if self:checkscope(section[".name"]) then
727 table.insert(sections, section[".name"])
728 end
729 end)
730
731 return sections
732 end
733
734 -- Limits scope to sections that have certain option => value pairs
735 function TypedSection.depends(self, option, value)
736 table.insert(self.deps, {option=option, value=value})
737 end
738
739 function TypedSection.parse(self)
740 if self.addremove then
741 -- Remove
742 local crval = REMOVE_PREFIX .. self.config
743 local name = luci.http.formvaluetable(crval)
744 for k,v in pairs(name) do
745 if k:sub(-2) == ".x" then
746 k = k:sub(1, #k - 2)
747 end
748 if self:cfgvalue(k) and self:checkscope(k) then
749 self:remove(k)
750 end
751 end
752 end
753
754 local co
755 for i, k in ipairs(self:cfgsections()) do
756 AbstractSection.parse_dynamic(self, k)
757 if luci.http.formvalue("cbi.submit") then
758 Node.parse(self, k)
759
760 if not self.override_scheme and self.map.scheme then
761 _uvl_validate_section(self, k)
762 end
763 end
764 AbstractSection.parse_optionals(self, k)
765 end
766
767 if self.addremove then
768 -- Create
769 local created
770 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
771 local name = luci.http.formvalue(crval)
772 if self.anonymous then
773 if name then
774 created = self:create()
775 end
776 else
777 if name then
778 -- Ignore if it already exists
779 if self:cfgvalue(name) then
780 name = nil;
781 end
782
783 name = self:checkscope(name)
784
785 if not name then
786 self.err_invalid = true
787 end
788
789 if name and #name > 0 then
790 created = self:create(name) and name
791 end
792 end
793 end
794
795 if created then
796 AbstractSection.parse_optionals(self, created)
797 end
798 end
799 end
800
801 -- Verifies scope of sections
802 function TypedSection.checkscope(self, section)
803 -- Check if we are not excluded
804 if self.filter and not self:filter(section) then
805 return nil
806 end
807
808 -- Check if at least one dependency is met
809 if #self.deps > 0 and self:cfgvalue(section) then
810 local stat = false
811
812 for k, v in ipairs(self.deps) do
813 if self:cfgvalue(section)[v.option] == v.value then
814 stat = true
815 end
816 end
817
818 if not stat then
819 return nil
820 end
821 end
822
823 return self:validate(section)
824 end
825
826
827 -- Dummy validate function
828 function TypedSection.validate(self, section)
829 return section
830 end
831
832
833 --[[
834 AbstractValue - An abstract Value Type
835 null: Value can be empty
836 valid: A function returning the value if it is valid otherwise nil
837 depends: A table of option => value pairs of which one must be true
838 default: The default value
839 size: The size of the input fields
840 rmempty: Unset value if empty
841 optional: This value is optional (see AbstractSection.optionals)
842 ]]--
843 AbstractValue = class(Node)
844
845 function AbstractValue.__init__(self, map, section, option, ...)
846 Node.__init__(self, ...)
847 self.section = section
848 self.option = option
849 self.map = map
850 self.config = map.config
851 self.tag_invalid = {}
852 self.tag_missing = {}
853 self.tag_reqerror = {}
854 self.tag_error = {}
855 self.deps = {}
856 self.cast = "string"
857
858 self.track_missing = false
859 self.rmempty = false
860 self.default = nil
861 self.size = nil
862 self.optional = false
863
864 -- Use defaults from UVL
865 if not self.override_scheme
866 and self.map:get_scheme(self.section.sectiontype, self.option) then
867 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
868 self.rmempty = not vs.required
869 self.cast = (vs.type == "list") and "list" or "string"
870 self.title = self.title or vs.title
871 self.description = self.description or vs.descr
872 self.default = vs.default
873
874 if vs.depends and not self.override_dependencies then
875 for i, deps in ipairs(vs.depends) do
876 deps = _uvl_strip_remote_dependencies(deps)
877 if next(deps) then
878 self:depends(deps)
879 end
880 end
881 end
882 end
883 end
884
885 -- Add a dependencie to another section field
886 function AbstractValue.depends(self, field, value)
887 local deps
888 if type(field) == "string" then
889 deps = {}
890 deps[field] = value
891 else
892 deps = field
893 end
894
895 table.insert(self.deps, {deps=deps, add=""})
896 end
897
898 -- Generates the unique CBID
899 function AbstractValue.cbid(self, section)
900 return "cbid."..self.map.config.."."..section.."."..self.option
901 end
902
903 -- Return whether this object should be created
904 function AbstractValue.formcreated(self, section)
905 local key = "cbi.opt."..self.config.."."..section
906 return (luci.http.formvalue(key) == self.option)
907 end
908
909 -- Returns the formvalue for this object
910 function AbstractValue.formvalue(self, section)
911 return luci.http.formvalue(self:cbid(section))
912 end
913
914 function AbstractValue.additional(self, value)
915 self.optional = value
916 end
917
918 function AbstractValue.mandatory(self, value)
919 self.rmempty = not value
920 end
921
922 function AbstractValue.parse(self, section)
923 local fvalue = self:formvalue(section)
924 local cvalue = self:cfgvalue(section)
925
926 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
927 fvalue = self:transform(self:validate(fvalue, section))
928 if not fvalue then
929 self.tag_invalid[section] = true
930 end
931 if fvalue and not (fvalue == cvalue) then
932 self:write(section, fvalue)
933 end
934 else -- Unset the UCI or error
935 if self.rmempty or self.optional then
936 self:remove(section)
937 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
938 self.tag_missing[section] = true
939 end
940 end
941 end
942
943 -- Render if this value exists or if it is mandatory
944 function AbstractValue.render(self, s, scope)
945 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
946 scope = scope or {}
947 scope.section = s
948 scope.cbid = self:cbid(s)
949 scope.striptags = luci.util.striptags
950
951 scope.ifattr = function(cond,key,val)
952 if cond then
953 return string.format(
954 ' %s="%s"', tostring(key),
955 luci.util.pcdata(tostring( val
956 or scope[key]
957 or (type(self[key]) ~= "function" and self[key])
958 or "" ))
959 )
960 else
961 return ''
962 end
963 end
964
965 scope.attr = function(...)
966 return scope.ifattr( true, ... )
967 end
968
969 Node.render(self, scope)
970 end
971 end
972
973 -- Return the UCI value of this object
974 function AbstractValue.cfgvalue(self, section)
975 local value = self.map:get(section, self.option)
976 if not self.cast or self.cast == type(value) then
977 return value
978 elseif self.cast == "string" then
979 if type(value) == "table" then
980 return value[1]
981 end
982 elseif self.cast == "table" then
983 return {value}
984 end
985 end
986
987 -- Validate the form value
988 function AbstractValue.validate(self, value)
989 return value
990 end
991
992 AbstractValue.transform = AbstractValue.validate
993
994
995 -- Write to UCI
996 function AbstractValue.write(self, section, value)
997 return self.map:set(section, self.option, value)
998 end
999
1000 -- Remove from UCI
1001 function AbstractValue.remove(self, section)
1002 return self.map:del(section, self.option)
1003 end
1004
1005
1006
1007
1008 --[[
1009 Value - A one-line value
1010 maxlength: The maximum length
1011 ]]--
1012 Value = class(AbstractValue)
1013
1014 function Value.__init__(self, ...)
1015 AbstractValue.__init__(self, ...)
1016 self.template = "cbi/value"
1017 self.keylist = {}
1018 self.vallist = {}
1019 end
1020
1021 function Value.value(self, key, val)
1022 val = val or key
1023 table.insert(self.keylist, tostring(key))
1024 table.insert(self.vallist, tostring(val))
1025 end
1026
1027
1028 -- DummyValue - This does nothing except being there
1029 DummyValue = class(AbstractValue)
1030
1031 function DummyValue.__init__(self, ...)
1032 AbstractValue.__init__(self, ...)
1033 self.template = "cbi/dvalue"
1034 self.value = nil
1035 end
1036
1037 function DummyValue.parse(self)
1038
1039 end
1040
1041
1042 --[[
1043 Flag - A flag being enabled or disabled
1044 ]]--
1045 Flag = class(AbstractValue)
1046
1047 function Flag.__init__(self, ...)
1048 AbstractValue.__init__(self, ...)
1049 self.template = "cbi/fvalue"
1050
1051 self.enabled = "1"
1052 self.disabled = "0"
1053 end
1054
1055 -- A flag can only have two states: set or unset
1056 function Flag.parse(self, section)
1057 local fvalue = self:formvalue(section)
1058
1059 if fvalue then
1060 fvalue = self.enabled
1061 else
1062 fvalue = self.disabled
1063 end
1064
1065 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1066 if not(fvalue == self:cfgvalue(section)) then
1067 self:write(section, fvalue)
1068 end
1069 else
1070 self:remove(section)
1071 end
1072 end
1073
1074
1075
1076 --[[
1077 ListValue - A one-line value predefined in a list
1078 widget: The widget that will be used (select, radio)
1079 ]]--
1080 ListValue = class(AbstractValue)
1081
1082 function ListValue.__init__(self, ...)
1083 AbstractValue.__init__(self, ...)
1084 self.template = "cbi/lvalue"
1085
1086 self.keylist = {}
1087 self.vallist = {}
1088 self.size = 1
1089 self.widget = "select"
1090
1091 if not self.override_scheme
1092 and self.map:get_scheme(self.section.sectiontype, self.option) then
1093 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1094 if self.value and vs.values and not self.override_values then
1095 if self.rmempty or self.optional then
1096 self:value("")
1097 end
1098 for k, v in pairs(vs.values) do
1099 local deps = {}
1100 if not self.override_dependencies
1101 and vs.enum_depends and vs.enum_depends[k] then
1102 for i, dep in ipairs(vs.enum_depends[k]) do
1103 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1104 end
1105 end
1106 self:value(k, v, unpack(deps))
1107 end
1108 end
1109 end
1110 end
1111
1112 function ListValue.value(self, key, val, ...)
1113 if luci.util.contains(self.keylist, key) then
1114 return
1115 end
1116
1117 val = val or key
1118 table.insert(self.keylist, tostring(key))
1119 table.insert(self.vallist, tostring(val))
1120
1121 for i, deps in ipairs({...}) do
1122 table.insert(self.deps, {add = "-"..key, deps=deps})
1123 end
1124 end
1125
1126 function ListValue.validate(self, val)
1127 if luci.util.contains(self.keylist, val) then
1128 return val
1129 else
1130 return nil
1131 end
1132 end
1133
1134
1135
1136 --[[
1137 MultiValue - Multiple delimited values
1138 widget: The widget that will be used (select, checkbox)
1139 delimiter: The delimiter that will separate the values (default: " ")
1140 ]]--
1141 MultiValue = class(AbstractValue)
1142
1143 function MultiValue.__init__(self, ...)
1144 AbstractValue.__init__(self, ...)
1145 self.template = "cbi/mvalue"
1146
1147 self.keylist = {}
1148 self.vallist = {}
1149
1150 self.widget = "checkbox"
1151 self.delimiter = " "
1152 end
1153
1154 function MultiValue.render(self, ...)
1155 if self.widget == "select" and not self.size then
1156 self.size = #self.vallist
1157 end
1158
1159 AbstractValue.render(self, ...)
1160 end
1161
1162 function MultiValue.value(self, key, val)
1163 if luci.util.contains(self.keylist, key) then
1164 return
1165 end
1166
1167 val = val or key
1168 table.insert(self.keylist, tostring(key))
1169 table.insert(self.vallist, tostring(val))
1170 end
1171
1172 function MultiValue.valuelist(self, section)
1173 local val = self:cfgvalue(section)
1174
1175 if not(type(val) == "string") then
1176 return {}
1177 end
1178
1179 return luci.util.split(val, self.delimiter)
1180 end
1181
1182 function MultiValue.validate(self, val)
1183 val = (type(val) == "table") and val or {val}
1184
1185 local result
1186
1187 for i, value in ipairs(val) do
1188 if luci.util.contains(self.keylist, value) then
1189 result = result and (result .. self.delimiter .. value) or value
1190 end
1191 end
1192
1193 return result
1194 end
1195
1196
1197 StaticList = class(MultiValue)
1198
1199 function StaticList.__init__(self, ...)
1200 MultiValue.__init__(self, ...)
1201 self.cast = "table"
1202 self.valuelist = self.cfgvalue
1203
1204 if not self.override_scheme
1205 and self.map:get_scheme(self.section.sectiontype, self.option) then
1206 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1207 if self.value and vs.values and not self.override_values then
1208 for k, v in pairs(vs.values) do
1209 self:value(k, v)
1210 end
1211 end
1212 end
1213 end
1214
1215 function StaticList.validate(self, value)
1216 value = (type(value) == "table") and value or {value}
1217
1218 local valid = {}
1219 for i, v in ipairs(value) do
1220 if luci.util.contains(self.valuelist, v) then
1221 table.insert(valid, v)
1222 end
1223 end
1224 return valid
1225 end
1226
1227
1228 DynamicList = class(AbstractValue)
1229
1230 function DynamicList.__init__(self, ...)
1231 AbstractValue.__init__(self, ...)
1232 self.template = "cbi/dynlist"
1233 self.cast = "table"
1234 self.keylist = {}
1235 self.vallist = {}
1236 end
1237
1238 function DynamicList.value(self, key, val)
1239 val = val or key
1240 table.insert(self.keylist, tostring(key))
1241 table.insert(self.vallist, tostring(val))
1242 end
1243
1244 function DynamicList.validate(self, value, section)
1245 value = (type(value) == "table") and value or {value}
1246
1247 local valid = {}
1248 for i, v in ipairs(value) do
1249 if v and #v > 0
1250 and not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i)
1251 and not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1252 table.insert(valid, v)
1253 end
1254 end
1255
1256 return valid
1257 end
1258
1259
1260 --[[
1261 TextValue - A multi-line value
1262 rows: Rows
1263 ]]--
1264 TextValue = class(AbstractValue)
1265
1266 function TextValue.__init__(self, ...)
1267 AbstractValue.__init__(self, ...)
1268 self.template = "cbi/tvalue"
1269 end
1270
1271 --[[
1272 Button
1273 ]]--
1274 Button = class(AbstractValue)
1275
1276 function Button.__init__(self, ...)
1277 AbstractValue.__init__(self, ...)
1278 self.template = "cbi/button"
1279 self.inputstyle = nil
1280 self.rmempty = true
1281 end