mirror of
https://github.com/bjc/prosody.git
synced 2025-04-03 21:27:38 +03:00
This is awkward but there’s currently no better way to do this, short of dynamically generating the entire form each time
301 lines
7.5 KiB
Lua
301 lines
7.5 KiB
Lua
-- Prosody IM
|
|
-- Copyright (C) 2008-2010 Matthew Wild
|
|
-- Copyright (C) 2008-2010 Waqas Hussain
|
|
--
|
|
-- This project is MIT/X11 licensed. Please see the
|
|
-- COPYING file in the source package for more information.
|
|
--
|
|
|
|
local setmetatable = setmetatable;
|
|
local ipairs = ipairs;
|
|
local type, next = type, next;
|
|
local t_concat = table.concat;
|
|
local st = require "util.stanza";
|
|
local jid_prep = require "util.jid".prep;
|
|
|
|
local _ENV = nil;
|
|
-- luacheck: std none
|
|
|
|
local xmlns_forms = 'jabber:x:data';
|
|
|
|
local form_t = {};
|
|
local form_mt = { __index = form_t };
|
|
|
|
local function new(layout)
|
|
return setmetatable(layout, form_mt);
|
|
end
|
|
|
|
function form_t.form(layout, data, formtype)
|
|
if not formtype then formtype = "form" end
|
|
local form = st.stanza("x", { xmlns = xmlns_forms, type = formtype });
|
|
if formtype == "cancel" then
|
|
return form;
|
|
end
|
|
if formtype ~= "submit" then
|
|
if layout.title then
|
|
form:tag("title"):text(layout.title):up();
|
|
end
|
|
if layout.instructions then
|
|
form:tag("instructions"):text(layout.instructions):up();
|
|
end
|
|
end
|
|
for _, field in ipairs(layout) do
|
|
local field_type = field.type or "text-single";
|
|
-- Add field tag
|
|
form:tag("field", { type = field_type, var = field.name, label = formtype ~= "submit" and field.label or nil });
|
|
|
|
if formtype ~= "submit" then
|
|
if field.desc then
|
|
form:text_tag("desc", field.desc);
|
|
end
|
|
end
|
|
|
|
local value;
|
|
if data and data[field.name] ~= nil then
|
|
value = data[field.name];
|
|
else
|
|
value = field.value;
|
|
end
|
|
|
|
local options = field.options;
|
|
if formtype == "form" and not options and value
|
|
and (field_type == "list-single" or field_type == "list-multi") then
|
|
-- Allow passing dynamically generated options as values
|
|
options, value = value, nil;
|
|
end
|
|
|
|
if formtype == "form" and options then
|
|
local defaults = {};
|
|
for _, val in ipairs(options) do
|
|
if type(val) == "table" then
|
|
form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up();
|
|
if val.default then
|
|
defaults[#defaults+1] = val.value;
|
|
end
|
|
else
|
|
form:tag("option", { label= val }):tag("value"):text(val):up():up();
|
|
end
|
|
end
|
|
if not value then
|
|
if field_type == "list-single" then
|
|
value = defaults[1];
|
|
elseif field_type == "list-multi" then
|
|
value = defaults;
|
|
end
|
|
end
|
|
end
|
|
|
|
if value ~= nil then
|
|
-- Add value, depending on type
|
|
if field_type == "hidden" then
|
|
if type(value) == "table" then
|
|
-- Assume an XML snippet
|
|
form:tag("value")
|
|
:add_child(value)
|
|
:up();
|
|
else
|
|
form:tag("value"):text(value):up();
|
|
end
|
|
elseif field_type == "boolean" then
|
|
form:tag("value"):text((value and "1") or "0"):up();
|
|
elseif field_type == "fixed" then
|
|
form:tag("value"):text(value):up();
|
|
elseif field_type == "jid-multi" then
|
|
for _, jid in ipairs(value) do
|
|
form:tag("value"):text(jid):up();
|
|
end
|
|
elseif field_type == "jid-single" then
|
|
form:tag("value"):text(value):up();
|
|
elseif field_type == "text-single" or field_type == "text-private" then
|
|
form:tag("value"):text(value):up();
|
|
elseif field_type == "text-multi" then
|
|
-- Split into multiple <value> tags, one for each line
|
|
for line in value:gmatch("([^\r\n]+)\r?\n*") do
|
|
form:tag("value"):text(line):up();
|
|
end
|
|
elseif field_type == "list-single" then
|
|
form:tag("value"):text(value):up();
|
|
elseif field_type == "list-multi" then
|
|
for _, val in ipairs(value) do
|
|
form:tag("value"):text(val):up();
|
|
end
|
|
end
|
|
end
|
|
|
|
local media = field.media;
|
|
if media then
|
|
form:tag("media", { xmlns = "urn:xmpp:media-element", height = media.height, width = media.width });
|
|
for _, val in ipairs(media) do
|
|
form:tag("uri", { type = val.type }):text(val.uri):up()
|
|
end
|
|
form:up();
|
|
end
|
|
|
|
if formtype == "form" and field.required then
|
|
form:tag("required"):up();
|
|
end
|
|
|
|
-- Jump back up to list of fields
|
|
form:up();
|
|
end
|
|
return form;
|
|
end
|
|
|
|
local field_readers = {};
|
|
|
|
function form_t.data(layout, stanza)
|
|
local data = {};
|
|
local errors = {};
|
|
local present = {};
|
|
|
|
for _, field in ipairs(layout) do
|
|
local tag;
|
|
for field_tag in stanza:childtags("field") do
|
|
if field.name == field_tag.attr.var then
|
|
tag = field_tag;
|
|
break;
|
|
end
|
|
end
|
|
|
|
if not tag then
|
|
if field.required then
|
|
errors[field.name] = "Required value missing";
|
|
end
|
|
elseif field.name then
|
|
present[field.name] = true;
|
|
local reader = field_readers[field.type];
|
|
if reader then
|
|
data[field.name], errors[field.name] = reader(tag, field.required);
|
|
end
|
|
end
|
|
end
|
|
if next(errors) then
|
|
return data, errors, present;
|
|
end
|
|
return data, nil, present;
|
|
end
|
|
|
|
local function simple_text(field_tag, required)
|
|
local data = field_tag:get_child_text("value");
|
|
-- XEP-0004 does not say if an empty string is acceptable for a required value
|
|
-- so we will follow HTML5 which says that empty string means missing
|
|
if required and (data == nil or data == "") then
|
|
return nil, "Required value missing";
|
|
end
|
|
return data; -- Return whatever get_child_text returned, even if empty string
|
|
end
|
|
|
|
field_readers["text-single"] = simple_text;
|
|
|
|
field_readers["text-private"] = simple_text;
|
|
|
|
field_readers["jid-single"] =
|
|
function (field_tag, required)
|
|
local raw_data, err = simple_text(field_tag, required);
|
|
if not raw_data then return raw_data, err; end
|
|
local data = jid_prep(raw_data);
|
|
if not data then
|
|
return nil, "Invalid JID: " .. raw_data;
|
|
end
|
|
return data;
|
|
end
|
|
|
|
field_readers["jid-multi"] =
|
|
function (field_tag, required)
|
|
local result = {};
|
|
local err = {};
|
|
for value_tag in field_tag:childtags("value") do
|
|
local raw_value = value_tag:get_text();
|
|
local value = jid_prep(raw_value);
|
|
result[#result+1] = value;
|
|
if raw_value and not value then
|
|
err[#err+1] = ("Invalid JID: " .. raw_value);
|
|
end
|
|
end
|
|
if #result > 0 then
|
|
return result, (#err > 0 and t_concat(err, "\n") or nil);
|
|
elseif required then
|
|
return nil, "Required value missing";
|
|
end
|
|
end
|
|
|
|
field_readers["list-multi"] =
|
|
function (field_tag, required)
|
|
local result = {};
|
|
for value in field_tag:childtags("value") do
|
|
result[#result+1] = value:get_text();
|
|
end
|
|
if #result > 0 then
|
|
return result;
|
|
elseif required then
|
|
return nil, "Required value missing";
|
|
end
|
|
end
|
|
|
|
field_readers["text-multi"] =
|
|
function (field_tag, required)
|
|
local data, err = field_readers["list-multi"](field_tag, required);
|
|
if data then
|
|
data = t_concat(data, "\n");
|
|
end
|
|
return data, err;
|
|
end
|
|
|
|
field_readers["list-single"] = simple_text;
|
|
|
|
local boolean_values = {
|
|
["1"] = true, ["true"] = true,
|
|
["0"] = false, ["false"] = false,
|
|
};
|
|
|
|
field_readers["boolean"] =
|
|
function (field_tag, required)
|
|
local raw_value, err = simple_text(field_tag, required);
|
|
if not raw_value then return raw_value, err; end
|
|
local value = boolean_values[raw_value];
|
|
if value == nil then
|
|
return nil, "Invalid boolean representation:" .. raw_value;
|
|
end
|
|
return value;
|
|
end
|
|
|
|
field_readers["hidden"] =
|
|
function (field_tag)
|
|
return field_tag:get_child_text("value");
|
|
end
|
|
|
|
|
|
local function get_form_type(form)
|
|
if not st.is_stanza(form) then
|
|
return nil, "not a stanza object";
|
|
elseif form.attr.xmlns ~= "jabber:x:data" or form.name ~= "x" then
|
|
return nil, "not a dataform element";
|
|
end
|
|
for field in form:childtags("field") do
|
|
if field.attr.var == "FORM_TYPE" then
|
|
return field:get_child_text("value");
|
|
end
|
|
end
|
|
return "";
|
|
end
|
|
|
|
return {
|
|
new = new;
|
|
get_type = get_form_type;
|
|
};
|
|
|
|
|
|
--[=[
|
|
|
|
Layout:
|
|
{
|
|
|
|
title = "MUC Configuration",
|
|
instructions = [[Use this form to configure options for this MUC room.]],
|
|
|
|
{ name = "FORM_TYPE", type = "hidden", required = true };
|
|
{ name = "field-name", type = "field-type", required = false };
|
|
}
|
|
|
|
|
|
--]=]
|