libs/web: export translatef() into templates
[project/luci.git] / libs / web / luasrc / dispatcher.lua
1 --[[
2 LuCI - Dispatcher
3
4 Description:
5 The request dispatcher and module dispatcher generators
6
7 FileId:
8 $Id$
9
10 License:
11 Copyright 2008 Steven Barth <steven@midlink.org>
12
13 Licensed under the Apache License, Version 2.0 (the "License");
14 you may not use this file except in compliance with the License.
15 You may obtain a copy of the License at
16
17 http://www.apache.org/licenses/LICENSE-2.0
18
19 Unless required by applicable law or agreed to in writing, software
20 distributed under the License is distributed on an "AS IS" BASIS,
21 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 See the License for the specific language governing permissions and
23 limitations under the License.
24
25 ]]--
26
27 --- LuCI web dispatcher.
28 local fs = require "nixio.fs"
29 local sys = require "luci.sys"
30 local init = require "luci.init"
31 local util = require "luci.util"
32 local http = require "luci.http"
33 local nixio = require "nixio", require "nixio.util"
34
35 module("luci.dispatcher", package.seeall)
36 context = util.threadlocal()
37 uci = require "luci.model.uci"
38 i18n = require "luci.i18n"
39 _M.fs = fs
40
41 authenticator = {}
42
43 -- Index table
44 local index = nil
45
46 -- Fastindex
47 local fi
48
49
50 --- Build the URL relative to the server webroot from given virtual path.
51 -- @param ... Virtual path
52 -- @return Relative URL
53 function build_url(...)
54 local path = {...}
55 local url = { http.getenv("SCRIPT_NAME") or "" }
56
57 local k, v
58 for k, v in pairs(context.urltoken) do
59 url[#url+1] = "/;"
60 url[#url+1] = http.urlencode(k)
61 url[#url+1] = "="
62 url[#url+1] = http.urlencode(v)
63 end
64
65 local p
66 for _, p in ipairs(path) do
67 if p:match("^[a-zA-Z0-9_%-%.%%/,;]+$") then
68 url[#url+1] = "/"
69 url[#url+1] = p
70 end
71 end
72
73 return table.concat(url, "")
74 end
75
76 --- Check whether a dispatch node shall be visible
77 -- @param node Dispatch node
78 -- @return Boolean indicating whether the node should be visible
79 function node_visible(node)
80 if node then
81 return not (
82 (not node.title or #node.title == 0) or
83 (not node.target or node.hidden == true) or
84 (type(node.target) == "table" and node.target.type == "firstchild" and
85 (type(node.nodes) ~= "table" or not next(node.nodes)))
86 )
87 end
88 return false
89 end
90
91 --- Return a sorted table of visible childs within a given node
92 -- @param node Dispatch node
93 -- @return Ordered table of child node names
94 function node_childs(node)
95 local rv = { }
96 if node then
97 local k, v
98 for k, v in util.spairs(node.nodes,
99 function(a, b)
100 return (node.nodes[a].order or 100)
101 < (node.nodes[b].order or 100)
102 end)
103 do
104 if node_visible(v) then
105 rv[#rv+1] = k
106 end
107 end
108 end
109 return rv
110 end
111
112
113 --- Send a 404 error code and render the "error404" template if available.
114 -- @param message Custom error message (optional)
115 -- @return false
116 function error404(message)
117 luci.http.status(404, "Not Found")
118 message = message or "Not Found"
119
120 require("luci.template")
121 if not luci.util.copcall(luci.template.render, "error404") then
122 luci.http.prepare_content("text/plain")
123 luci.http.write(message)
124 end
125 return false
126 end
127
128 --- Send a 500 error code and render the "error500" template if available.
129 -- @param message Custom error message (optional)#
130 -- @return false
131 function error500(message)
132 luci.util.perror(message)
133 if not context.template_header_sent then
134 luci.http.status(500, "Internal Server Error")
135 luci.http.prepare_content("text/plain")
136 luci.http.write(message)
137 else
138 require("luci.template")
139 if not luci.util.copcall(luci.template.render, "error500", {message=message}) then
140 luci.http.prepare_content("text/plain")
141 luci.http.write(message)
142 end
143 end
144 return false
145 end
146
147 function authenticator.htmlauth(validator, accs, default)
148 local user = luci.http.formvalue("username")
149 local pass = luci.http.formvalue("password")
150
151 if user and validator(user, pass) then
152 return user
153 end
154
155 require("luci.i18n")
156 require("luci.template")
157 context.path = {}
158 luci.template.render("sysauth", {duser=default, fuser=user})
159 return false
160
161 end
162
163 --- Dispatch an HTTP request.
164 -- @param request LuCI HTTP Request object
165 function httpdispatch(request, prefix)
166 luci.http.context.request = request
167
168 local r = {}
169 context.request = r
170 context.urltoken = {}
171
172 local pathinfo = http.urldecode(request:getenv("PATH_INFO") or "", true)
173
174 if prefix then
175 for _, node in ipairs(prefix) do
176 r[#r+1] = node
177 end
178 end
179
180 local tokensok = true
181 for node in pathinfo:gmatch("[^/]+") do
182 local tkey, tval
183 if tokensok then
184 tkey, tval = node:match(";(%w+)=([a-fA-F0-9]*)")
185 end
186 if tkey then
187 context.urltoken[tkey] = tval
188 else
189 tokensok = false
190 r[#r+1] = node
191 end
192 end
193
194 local stat, err = util.coxpcall(function()
195 dispatch(context.request)
196 end, error500)
197
198 luci.http.close()
199
200 --context._disable_memtrace()
201 end
202
203 --- Dispatches a LuCI virtual path.
204 -- @param request Virtual path
205 function dispatch(request)
206 --context._disable_memtrace = require "luci.debug".trap_memtrace("l")
207 local ctx = context
208 ctx.path = request
209
210 local conf = require "luci.config"
211 assert(conf.main,
212 "/etc/config/luci seems to be corrupt, unable to find section 'main'")
213
214 local lang = conf.main.lang or "auto"
215 if lang == "auto" then
216 local aclang = http.getenv("HTTP_ACCEPT_LANGUAGE") or ""
217 for lpat in aclang:gmatch("[%w-]+") do
218 lpat = lpat and lpat:gsub("-", "_")
219 if conf.languages[lpat] then
220 lang = lpat
221 break
222 end
223 end
224 end
225 require "luci.i18n".setlanguage(lang)
226
227 local c = ctx.tree
228 local stat
229 if not c then
230 c = createtree()
231 end
232
233 local track = {}
234 local args = {}
235 ctx.args = args
236 ctx.requestargs = ctx.requestargs or args
237 local n
238 local token = ctx.urltoken
239 local preq = {}
240 local freq = {}
241
242 for i, s in ipairs(request) do
243 preq[#preq+1] = s
244 freq[#freq+1] = s
245 c = c.nodes[s]
246 n = i
247 if not c then
248 break
249 end
250
251 util.update(track, c)
252
253 if c.leaf then
254 break
255 end
256 end
257
258 if c and c.leaf then
259 for j=n+1, #request do
260 args[#args+1] = request[j]
261 freq[#freq+1] = request[j]
262 end
263 end
264
265 ctx.requestpath = ctx.requestpath or freq
266 ctx.path = preq
267
268 if track.i18n then
269 i18n.loadc(track.i18n)
270 end
271
272 -- Init template engine
273 if (c and c.index) or not track.notemplate then
274 local tpl = require("luci.template")
275 local media = track.mediaurlbase or luci.config.main.mediaurlbase
276 if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then
277 media = nil
278 for name, theme in pairs(luci.config.themes) do
279 if name:sub(1,1) ~= "." and pcall(tpl.Template,
280 "themes/%s/header" % fs.basename(theme)) then
281 media = theme
282 end
283 end
284 assert(media, "No valid theme found")
285 end
286
287 local function _ifattr(cond, key, val)
288 if cond then
289 local env = getfenv(3)
290 local scope = (type(env.self) == "table") and env.self
291 return string.format(
292 ' %s="%s"', tostring(key),
293 luci.util.pcdata(tostring( val
294 or (type(env[key]) ~= "function" and env[key])
295 or (scope and type(scope[key]) ~= "function" and scope[key])
296 or "" ))
297 )
298 else
299 return ''
300 end
301 end
302
303 tpl.context.viewns = setmetatable({
304 write = luci.http.write;
305 include = function(name) tpl.Template(name):render(getfenv(2)) end;
306 translate = i18n.translate;
307 translatef = i18n.translatef;
308 export = function(k, v) if tpl.context.viewns[k] == nil then tpl.context.viewns[k] = v end end;
309 striptags = util.striptags;
310 pcdata = util.pcdata;
311 media = media;
312 theme = fs.basename(media);
313 resource = luci.config.main.resourcebase;
314 ifattr = function(...) return _ifattr(...) end;
315 attr = function(...) return _ifattr(true, ...) end;
316 }, {__index=function(table, key)
317 if key == "controller" then
318 return build_url()
319 elseif key == "REQUEST_URI" then
320 return build_url(unpack(ctx.requestpath))
321 else
322 return rawget(table, key) or _G[key]
323 end
324 end})
325 end
326
327 track.dependent = (track.dependent ~= false)
328 assert(not track.dependent or not track.auto,
329 "Access Violation\nThe page at '" .. table.concat(request, "/") .. "/' " ..
330 "has no parent node so the access to this location has been denied.\n" ..
331 "This is a software bug, please report this message at " ..
332 "http://luci.subsignal.org/trac/newticket"
333 )
334
335 if track.sysauth then
336 local sauth = require "luci.sauth"
337
338 local authen = type(track.sysauth_authenticator) == "function"
339 and track.sysauth_authenticator
340 or authenticator[track.sysauth_authenticator]
341
342 local def = (type(track.sysauth) == "string") and track.sysauth
343 local accs = def and {track.sysauth} or track.sysauth
344 local sess = ctx.authsession
345 local verifytoken = false
346 if not sess then
347 sess = luci.http.getcookie("sysauth")
348 sess = sess and sess:match("^[a-f0-9]*$")
349 verifytoken = true
350 end
351
352 local sdat = sauth.read(sess)
353 local user
354
355 if sdat then
356 if not verifytoken or ctx.urltoken.stok == sdat.token then
357 user = sdat.user
358 end
359 else
360 local eu = http.getenv("HTTP_AUTH_USER")
361 local ep = http.getenv("HTTP_AUTH_PASS")
362 if eu and ep and luci.sys.user.checkpasswd(eu, ep) then
363 authen = function() return eu end
364 end
365 end
366
367 if not util.contains(accs, user) then
368 if authen then
369 ctx.urltoken.stok = nil
370 local user, sess = authen(luci.sys.user.checkpasswd, accs, def)
371 if not user or not util.contains(accs, user) then
372 return
373 else
374 local sid = sess or luci.sys.uniqueid(16)
375 if not sess then
376 local token = luci.sys.uniqueid(16)
377 sauth.reap()
378 sauth.write(sid, {
379 user=user,
380 token=token,
381 secret=luci.sys.uniqueid(16)
382 })
383 ctx.urltoken.stok = token
384 end
385 luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path="..build_url())
386 ctx.authsession = sid
387 ctx.authuser = user
388 end
389 else
390 luci.http.status(403, "Forbidden")
391 return
392 end
393 else
394 ctx.authsession = sess
395 ctx.authuser = user
396 end
397 end
398
399 if track.setgroup then
400 luci.sys.process.setgroup(track.setgroup)
401 end
402
403 if track.setuser then
404 luci.sys.process.setuser(track.setuser)
405 end
406
407 local target = nil
408 if c then
409 if type(c.target) == "function" then
410 target = c.target
411 elseif type(c.target) == "table" then
412 target = c.target.target
413 end
414 end
415
416 if c and (c.index or type(target) == "function") then
417 ctx.dispatched = c
418 ctx.requested = ctx.requested or ctx.dispatched
419 end
420
421 if c and c.index then
422 local tpl = require "luci.template"
423
424 if util.copcall(tpl.render, "indexer", {}) then
425 return true
426 end
427 end
428
429 if type(target) == "function" then
430 util.copcall(function()
431 local oldenv = getfenv(target)
432 local module = require(c.module)
433 local env = setmetatable({}, {__index=
434
435 function(tbl, key)
436 return rawget(tbl, key) or module[key] or oldenv[key]
437 end})
438
439 setfenv(target, env)
440 end)
441
442 local ok, err
443 if type(c.target) == "table" then
444 ok, err = util.copcall(target, c.target, unpack(args))
445 else
446 ok, err = util.copcall(target, unpack(args))
447 end
448 assert(ok,
449 "Failed to execute " .. (type(c.target) == "function" and "function" or c.target.type or "unknown") ..
450 " dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" ..
451 "The called action terminated with an exception:\n" .. tostring(err or "(unknown)"))
452 else
453 local root = node()
454 if not root or not root.target then
455 error404("No root node was registered, this usually happens if no module was installed.\n" ..
456 "Install luci-mod-admin-full and retry. " ..
457 "If the module is already installed, try removing the /tmp/luci-indexcache file.")
458 else
459 error404("No page is registered at '/" .. table.concat(request, "/") .. "'.\n" ..
460 "If this url belongs to an extension, make sure it is properly installed.\n" ..
461 "If the extension was recently installed, try removing the /tmp/luci-indexcache file.")
462 end
463 end
464 end
465
466 --- Generate the dispatching index using the best possible strategy.
467 function createindex()
468 local path = luci.util.libpath() .. "/controller/"
469 local suff = { ".lua", ".lua.gz" }
470
471 if luci.util.copcall(require, "luci.fastindex") then
472 createindex_fastindex(path, suff)
473 else
474 createindex_plain(path, suff)
475 end
476 end
477
478 --- Generate the dispatching index using the fastindex C-indexer.
479 -- @param path Controller base directory
480 -- @param suffixes Controller file suffixes
481 function createindex_fastindex(path, suffixes)
482 index = {}
483
484 if not fi then
485 fi = luci.fastindex.new("index")
486 for _, suffix in ipairs(suffixes) do
487 fi.add(path .. "*" .. suffix)
488 fi.add(path .. "*/*" .. suffix)
489 end
490 end
491 fi.scan()
492
493 for k, v in pairs(fi.indexes) do
494 index[v[2]] = v[1]
495 end
496 end
497
498 --- Generate the dispatching index using the native file-cache based strategy.
499 -- @param path Controller base directory
500 -- @param suffixes Controller file suffixes
501 function createindex_plain(path, suffixes)
502 local controllers = { }
503 for _, suffix in ipairs(suffixes) do
504 nixio.util.consume((fs.glob(path .. "*" .. suffix)), controllers)
505 nixio.util.consume((fs.glob(path .. "*/*" .. suffix)), controllers)
506 end
507
508 if indexcache then
509 local cachedate = fs.stat(indexcache, "mtime")
510 if cachedate then
511 local realdate = 0
512 for _, obj in ipairs(controllers) do
513 local omtime = fs.stat(obj, "mtime")
514 realdate = (omtime and omtime > realdate) and omtime or realdate
515 end
516
517 if cachedate > realdate then
518 assert(
519 sys.process.info("uid") == fs.stat(indexcache, "uid")
520 and fs.stat(indexcache, "modestr") == "rw-------",
521 "Fatal: Indexcache is not sane!"
522 )
523
524 index = loadfile(indexcache)()
525 return index
526 end
527 end
528 end
529
530 index = {}
531
532 for i,c in ipairs(controllers) do
533 local modname = "luci.controller." .. c:sub(#path+1, #c):gsub("/", ".")
534 for _, suffix in ipairs(suffixes) do
535 modname = modname:gsub(suffix.."$", "")
536 end
537
538 local mod = require(modname)
539 assert(mod ~= true,
540 "Invalid controller file found\n" ..
541 "The file '" .. c .. "' contains an invalid module line.\n" ..
542 "Please verify whether the module name is set to '" .. modname ..
543 "' - It must correspond to the file path!")
544
545 local idx = mod.index
546 assert(type(idx) == "function",
547 "Invalid controller file found\n" ..
548 "The file '" .. c .. "' contains no index() function.\n" ..
549 "Please make sure that the controller contains a valid " ..
550 "index function and verify the spelling!")
551
552 index[modname] = idx
553 end
554
555 if indexcache then
556 local f = nixio.open(indexcache, "w", 600)
557 f:writeall(util.get_bytecode(index))
558 f:close()
559 end
560 end
561
562 --- Create the dispatching tree from the index.
563 -- Build the index before if it does not exist yet.
564 function createtree()
565 if not index then
566 createindex()
567 end
568
569 local ctx = context
570 local tree = {nodes={}, inreq=true}
571 local modi = {}
572
573 ctx.treecache = setmetatable({}, {__mode="v"})
574 ctx.tree = tree
575 ctx.modifiers = modi
576
577 -- Load default translation
578 require "luci.i18n".loadc("base")
579
580 local scope = setmetatable({}, {__index = luci.dispatcher})
581
582 for k, v in pairs(index) do
583 scope._NAME = k
584 setfenv(v, scope)
585 v()
586 end
587
588 local function modisort(a,b)
589 return modi[a].order < modi[b].order
590 end
591
592 for _, v in util.spairs(modi, modisort) do
593 scope._NAME = v.module
594 setfenv(v.func, scope)
595 v.func()
596 end
597
598 return tree
599 end
600
601 --- Register a tree modifier.
602 -- @param func Modifier function
603 -- @param order Modifier order value (optional)
604 function modifier(func, order)
605 context.modifiers[#context.modifiers+1] = {
606 func = func,
607 order = order or 0,
608 module
609 = getfenv(2)._NAME
610 }
611 end
612
613 --- Clone a node of the dispatching tree to another position.
614 -- @param path Virtual path destination
615 -- @param clone Virtual path source
616 -- @param title Destination node title (optional)
617 -- @param order Destination node order value (optional)
618 -- @return Dispatching tree node
619 function assign(path, clone, title, order)
620 local obj = node(unpack(path))
621 obj.nodes = nil
622 obj.module = nil
623
624 obj.title = title
625 obj.order = order
626
627 setmetatable(obj, {__index = _create_node(clone)})
628
629 return obj
630 end
631
632 --- Create a new dispatching node and define common parameters.
633 -- @param path Virtual path
634 -- @param target Target function to call when dispatched.
635 -- @param title Destination node title
636 -- @param order Destination node order value (optional)
637 -- @return Dispatching tree node
638 function entry(path, target, title, order)
639 local c = node(unpack(path))
640
641 c.target = target
642 c.title = title
643 c.order = order
644 c.module = getfenv(2)._NAME
645
646 return c
647 end
648
649 --- Fetch or create a dispatching node without setting the target module or
650 -- enabling the node.
651 -- @param ... Virtual path
652 -- @return Dispatching tree node
653 function get(...)
654 return _create_node({...})
655 end
656
657 --- Fetch or create a new dispatching node.
658 -- @param ... Virtual path
659 -- @return Dispatching tree node
660 function node(...)
661 local c = _create_node({...})
662
663 c.module = getfenv(2)._NAME
664 c.auto = nil
665
666 return c
667 end
668
669 function _create_node(path)
670 if #path == 0 then
671 return context.tree
672 end
673
674 local name = table.concat(path, ".")
675 local c = context.treecache[name]
676
677 if not c then
678 local last = table.remove(path)
679 local parent = _create_node(path)
680
681 c = {nodes={}, auto=true}
682 -- the node is "in request" if the request path matches
683 -- at least up to the length of the node path
684 if parent.inreq and context.path[#path+1] == last then
685 c.inreq = true
686 end
687 parent.nodes[last] = c
688 context.treecache[name] = c
689 end
690 return c
691 end
692
693 -- Subdispatchers --
694
695 function _firstchild()
696 local path = { unpack(context.path) }
697 local name = table.concat(path, ".")
698 local node = context.treecache[name]
699
700 local lowest
701 if node and node.nodes and next(node.nodes) then
702 local k, v
703 for k, v in pairs(node.nodes) do
704 if not lowest or
705 (v.order or 100) < (node.nodes[lowest].order or 100)
706 then
707 lowest = k
708 end
709 end
710 end
711
712 assert(lowest ~= nil,
713 "The requested node contains no childs, unable to redispatch")
714
715 path[#path+1] = lowest
716 dispatch(path)
717 end
718
719 --- Alias the first (lowest order) page automatically
720 function firstchild()
721 return { type = "firstchild", target = _firstchild }
722 end
723
724 --- Create a redirect to another dispatching node.
725 -- @param ... Virtual path destination
726 function alias(...)
727 local req = {...}
728 return function(...)
729 for _, r in ipairs({...}) do
730 req[#req+1] = r
731 end
732
733 dispatch(req)
734 end
735 end
736
737 --- Rewrite the first x path values of the request.
738 -- @param n Number of path values to replace
739 -- @param ... Virtual path to replace removed path values with
740 function rewrite(n, ...)
741 local req = {...}
742 return function(...)
743 local dispatched = util.clone(context.dispatched)
744
745 for i=1,n do
746 table.remove(dispatched, 1)
747 end
748
749 for i, r in ipairs(req) do
750 table.insert(dispatched, i, r)
751 end
752
753 for _, r in ipairs({...}) do
754 dispatched[#dispatched+1] = r
755 end
756
757 dispatch(dispatched)
758 end
759 end
760
761
762 local function _call(self, ...)
763 local func = getfenv()[self.name]
764 assert(func ~= nil,
765 'Cannot resolve function "' .. self.name .. '". Is it misspelled or local?')
766
767 assert(type(func) == "function",
768 'The symbol "' .. self.name .. '" does not refer to a function but data ' ..
769 'of type "' .. type(func) .. '".')
770
771 if #self.argv > 0 then
772 return func(unpack(self.argv), ...)
773 else
774 return func(...)
775 end
776 end
777
778 --- Create a function-call dispatching target.
779 -- @param name Target function of local controller
780 -- @param ... Additional parameters passed to the function
781 function call(name, ...)
782 return {type = "call", argv = {...}, name = name, target = _call}
783 end
784
785
786 local _template = function(self, ...)
787 require "luci.template".render(self.view)
788 end
789
790 --- Create a template render dispatching target.
791 -- @param name Template to be rendered
792 function template(name)
793 return {type = "template", view = name, target = _template}
794 end
795
796
797 local function _cbi(self, ...)
798 local cbi = require "luci.cbi"
799 local tpl = require "luci.template"
800 local http = require "luci.http"
801
802 local config = self.config or {}
803 local maps = cbi.load(self.model, ...)
804
805 local state = nil
806
807 for i, res in ipairs(maps) do
808 res.flow = config
809 local cstate = res:parse()
810 if cstate and (not state or cstate < state) then
811 state = cstate
812 end
813 end
814
815 local function _resolve_path(path)
816 return type(path) == "table" and build_url(unpack(path)) or path
817 end
818
819 if config.on_valid_to and state and state > 0 and state < 2 then
820 http.redirect(_resolve_path(config.on_valid_to))
821 return
822 end
823
824 if config.on_changed_to and state and state > 1 then
825 http.redirect(_resolve_path(config.on_changed_to))
826 return
827 end
828
829 if config.on_success_to and state and state > 0 then
830 http.redirect(_resolve_path(config.on_success_to))
831 return
832 end
833
834 if config.state_handler then
835 if not config.state_handler(state, maps) then
836 return
837 end
838 end
839
840 http.header("X-CBI-State", state or 0)
841
842 if not config.noheader then
843 tpl.render("cbi/header", {state = state})
844 end
845
846 local redirect
847 local messages
848 local applymap = false
849 local pageaction = true
850 local parsechain = { }
851
852 for i, res in ipairs(maps) do
853 if res.apply_needed and res.parsechain then
854 local c
855 for _, c in ipairs(res.parsechain) do
856 parsechain[#parsechain+1] = c
857 end
858 applymap = true
859 end
860
861 if res.redirect then
862 redirect = redirect or res.redirect
863 end
864
865 if res.pageaction == false then
866 pageaction = false
867 end
868
869 if res.message then
870 messages = messages or { }
871 messages[#messages+1] = res.message
872 end
873 end
874
875 for i, res in ipairs(maps) do
876 res:render({
877 firstmap = (i == 1),
878 applymap = applymap,
879 redirect = redirect,
880 messages = messages,
881 pageaction = pageaction,
882 parsechain = parsechain
883 })
884 end
885
886 if not config.nofooter then
887 tpl.render("cbi/footer", {
888 flow = config,
889 pageaction = pageaction,
890 redirect = redirect,
891 state = state,
892 autoapply = config.autoapply
893 })
894 end
895 end
896
897 --- Create a CBI model dispatching target.
898 -- @param model CBI model to be rendered
899 function cbi(model, config)
900 return {type = "cbi", config = config, model = model, target = _cbi}
901 end
902
903
904 local function _arcombine(self, ...)
905 local argv = {...}
906 local target = #argv > 0 and self.targets[2] or self.targets[1]
907 setfenv(target.target, self.env)
908 target:target(unpack(argv))
909 end
910
911 --- Create a combined dispatching target for non argv and argv requests.
912 -- @param trg1 Overview Target
913 -- @param trg2 Detail Target
914 function arcombine(trg1, trg2)
915 return {type = "arcombine", env = getfenv(), target = _arcombine, targets = {trg1, trg2}}
916 end
917
918
919 local function _form(self, ...)
920 local cbi = require "luci.cbi"
921 local tpl = require "luci.template"
922 local http = require "luci.http"
923
924 local maps = luci.cbi.load(self.model, ...)
925 local state = nil
926
927 for i, res in ipairs(maps) do
928 local cstate = res:parse()
929 if cstate and (not state or cstate < state) then
930 state = cstate
931 end
932 end
933
934 http.header("X-CBI-State", state or 0)
935 tpl.render("header")
936 for i, res in ipairs(maps) do
937 res:render()
938 end
939 tpl.render("footer")
940 end
941
942 --- Create a CBI form model dispatching target.
943 -- @param model CBI form model tpo be rendered
944 function form(model)
945 return {type = "cbi", model = model, target = _form}
946 end
947
948 --- Access the luci.i18n translate() api.
949 -- @class function
950 -- @name translate
951 -- @param text Text to translate
952 translate = i18n.translate
953
954 --- No-op function used to mark translation entries for menu labels.
955 -- This function does not actually translate the given argument but
956 -- is used by build/i18n-scan.pl to find translatable entries.
957 function _(text)
958 return text
959 end