Prepare required data folder for integration tests
[prosody.git] / util / dataforms.lua
blob052d6a552a33b3c199cc0ff7003110a6963211e7
1 -- Prosody IM
2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
4 --
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
7 --
9 local setmetatable = setmetatable;
10 local ipairs = ipairs;
11 local type, next = type, next;
12 local tonumber = tonumber;
13 local t_concat = table.concat;
14 local st = require "util.stanza";
15 local jid_prep = require "util.jid".prep;
17 local _ENV = nil;
18 -- luacheck: std none
20 local xmlns_forms = 'jabber:x:data';
21 local xmlns_validate = 'http://jabber.org/protocol/xdata-validate';
23 local form_t = {};
24 local form_mt = { __index = form_t };
26 local function new(layout)
27 return setmetatable(layout, form_mt);
28 end
30 function form_t.form(layout, data, formtype)
31 if not formtype then formtype = "form" end
32 local form = st.stanza("x", { xmlns = xmlns_forms, type = formtype });
33 if formtype == "cancel" then
34 return form;
35 end
36 if formtype ~= "submit" then
37 if layout.title then
38 form:tag("title"):text(layout.title):up();
39 end
40 if layout.instructions then
41 form:tag("instructions"):text(layout.instructions):up();
42 end
43 end
44 for _, field in ipairs(layout) do
45 local field_type = field.type or "text-single";
46 -- Add field tag
47 form:tag("field", { type = field_type, var = field.var or field.name, label = formtype ~= "submit" and field.label or nil });
49 if formtype ~= "submit" then
50 if field.desc then
51 form:text_tag("desc", field.desc);
52 end
53 end
55 if formtype == "form" and field.datatype then
56 form:tag("validate", { xmlns = xmlns_validate, datatype = field.datatype });
57 -- <basic/> assumed
58 form:up();
59 end
62 local value = field.value;
63 local options = field.options;
65 if data and data[field.name] ~= nil then
66 value = data[field.name];
68 if formtype == "form" and type(value) == "table"
69 and (field_type == "list-single" or field_type == "list-multi") then
70 -- Allow passing dynamically generated options as values
71 options, value = value, nil;
72 end
73 end
75 if formtype == "form" and options then
76 local defaults = {};
77 for _, val in ipairs(options) do
78 if type(val) == "table" then
79 form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up();
80 if val.default then
81 defaults[#defaults+1] = val.value;
82 end
83 else
84 form:tag("option", { label= val }):tag("value"):text(val):up():up();
85 end
86 end
87 if not value then
88 if field_type == "list-single" then
89 value = defaults[1];
90 elseif field_type == "list-multi" then
91 value = defaults;
92 end
93 end
94 end
96 if value ~= nil then
97 if type(value) == "number" then
98 -- TODO validate that this is ok somehow, eg check field.datatype
99 value = ("%g"):format(value);
101 -- Add value, depending on type
102 if field_type == "hidden" then
103 if type(value) == "table" then
104 -- Assume an XML snippet
105 form:tag("value")
106 :add_child(value)
107 :up();
108 else
109 form:tag("value"):text(value):up();
111 elseif field_type == "boolean" then
112 form:tag("value"):text((value and "1") or "0"):up();
113 elseif field_type == "fixed" then
114 form:tag("value"):text(value):up();
115 elseif field_type == "jid-multi" then
116 for _, jid in ipairs(value) do
117 form:tag("value"):text(jid):up();
119 elseif field_type == "jid-single" then
120 form:tag("value"):text(value):up();
121 elseif field_type == "text-single" or field_type == "text-private" then
122 form:tag("value"):text(value):up();
123 elseif field_type == "text-multi" then
124 -- Split into multiple <value> tags, one for each line
125 for line in value:gmatch("([^\r\n]+)\r?\n*") do
126 form:tag("value"):text(line):up();
128 elseif field_type == "list-single" then
129 form:tag("value"):text(value):up();
130 elseif field_type == "list-multi" then
131 for _, val in ipairs(value) do
132 form:tag("value"):text(val):up();
137 local media = field.media;
138 if media then
139 form:tag("media", { xmlns = "urn:xmpp:media-element", height = media.height, width = media.width });
140 for _, val in ipairs(media) do
141 form:tag("uri", { type = val.type }):text(val.uri):up()
143 form:up();
146 if formtype == "form" and field.required then
147 form:tag("required"):up();
150 -- Jump back up to list of fields
151 form:up();
153 return form;
156 local field_readers = {};
157 local data_validators = {};
159 function form_t.data(layout, stanza, current)
160 local data = {};
161 local errors = {};
162 local present = {};
164 for _, field in ipairs(layout) do
165 local tag;
166 for field_tag in stanza:childtags("field") do
167 if (field.var or field.name) == field_tag.attr.var then
168 tag = field_tag;
169 break;
173 if not tag then
174 if current and current[field.name] ~= nil then
175 data[field.name] = current[field.name];
176 elseif field.required then
177 errors[field.name] = "Required value missing";
179 elseif field.name then
180 present[field.name] = true;
181 local reader = field_readers[field.type];
182 if reader then
183 local value, err = reader(tag, field.required);
184 local validator = field.datatype and data_validators[field.datatype];
185 if value ~= nil and validator then
186 local valid, ret = validator(value, field);
187 if valid then
188 value = ret;
189 else
190 value, err = nil, ret or ("Invalid value for data of type " .. field.datatype);
193 data[field.name], errors[field.name] = value, err;
197 if next(errors) then
198 return data, errors, present;
200 return data, nil, present;
203 local function simple_text(field_tag, required)
204 local data = field_tag:get_child_text("value");
205 -- XEP-0004 does not say if an empty string is acceptable for a required value
206 -- so we will follow HTML5 which says that empty string means missing
207 if required and (data == nil or data == "") then
208 return nil, "Required value missing";
210 return data; -- Return whatever get_child_text returned, even if empty string
213 field_readers["text-single"] = simple_text;
215 field_readers["text-private"] = simple_text;
217 field_readers["jid-single"] =
218 function (field_tag, required)
219 local raw_data, err = simple_text(field_tag, required);
220 if not raw_data then return raw_data, err; end
221 local data = jid_prep(raw_data);
222 if not data then
223 return nil, "Invalid JID: " .. raw_data;
225 return data;
228 field_readers["jid-multi"] =
229 function (field_tag, required)
230 local result = {};
231 local err = {};
232 for value_tag in field_tag:childtags("value") do
233 local raw_value = value_tag:get_text();
234 local value = jid_prep(raw_value);
235 result[#result+1] = value;
236 if raw_value and not value then
237 err[#err+1] = ("Invalid JID: " .. raw_value);
240 if #result > 0 then
241 return result, (#err > 0 and t_concat(err, "\n") or nil);
242 elseif required then
243 return nil, "Required value missing";
247 field_readers["list-multi"] =
248 function (field_tag, required)
249 local result = {};
250 for value in field_tag:childtags("value") do
251 result[#result+1] = value:get_text();
253 if #result > 0 then
254 return result;
255 elseif required then
256 return nil, "Required value missing";
260 field_readers["text-multi"] =
261 function (field_tag, required)
262 local data, err = field_readers["list-multi"](field_tag, required);
263 if data then
264 data = t_concat(data, "\n");
266 return data, err;
269 field_readers["list-single"] = simple_text;
271 local boolean_values = {
272 ["1"] = true, ["true"] = true,
273 ["0"] = false, ["false"] = false,
276 field_readers["boolean"] =
277 function (field_tag, required)
278 local raw_value, err = simple_text(field_tag, required);
279 if not raw_value then return raw_value, err; end
280 local value = boolean_values[raw_value];
281 if value == nil then
282 return nil, "Invalid boolean representation:" .. raw_value;
284 return value;
287 field_readers["hidden"] =
288 function (field_tag)
289 return field_tag:get_child_text("value");
292 data_validators["xs:integer"] =
293 function (data)
294 local n = tonumber(data);
295 if not n then
296 return false, "not a number";
297 elseif n % 1 ~= 0 then
298 return false, "not an integer";
300 return true, n;
304 local function get_form_type(form)
305 if not st.is_stanza(form) then
306 return nil, "not a stanza object";
307 elseif form.attr.xmlns ~= "jabber:x:data" or form.name ~= "x" then
308 return nil, "not a dataform element";
310 for field in form:childtags("field") do
311 if field.attr.var == "FORM_TYPE" then
312 return field:get_child_text("value");
315 return "";
318 return {
319 new = new;
320 get_type = get_form_type;
324 --[=[
326 Layout:
329 title = "MUC Configuration",
330 instructions = [[Use this form to configure options for this MUC room.]],
332 { name = "FORM_TYPE", type = "hidden", required = true };
333 { name = "field-name", type = "field-type", required = false };
337 --]=]