File

util/dataforms.lua @ 7567:495de404a8ae

ejabberdsql2prosody: rename variable 'host' to prevent shadowing upvalue [luacheck] Functions roster(), roster_pending(), roster_group(), private_storage() and offline_msg() have argument named "host", which used to shadow upvalue of this variable before this change. Instead of renaming this argument, let's rename the variable to match what the script says in usage: Usage: ejabberdsql2prosody.lua filename.txt hostname
author Anton Shestakov <av6@dwimlabs.net>
date Fri, 12 Aug 2016 13:44:47 +0800
parent 7446:0db72fac2faa
child 8255:d70d4c1ac17a
line wrap: on
line source

-- 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 tostring, type, next = tostring, type, next;
local t_concat = table.concat;
local st = require "util.stanza";
local jid_prep = require "util.jid".prep;

local _ENV = nil;

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)
	local form = st.stanza("x", { xmlns = xmlns_forms, type = formtype or "form" });
	if layout.title then
		form:tag("title"):text(layout.title):up();
	end
	if layout.instructions then
		form:tag("instructions"):text(layout.instructions):up();
	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 = field.label });

		local value = (data and data[field.name]) or field.value;

		if value 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(tostring(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
				local has_default = false;
				for _, val in ipairs(field.options or value) do
					if type(val) == "table" then
						form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up();
						if value == val.value or val.default and (not has_default) then
							form:tag("value"):text(val.value):up();
							has_default = true;
						end
					else
						form:tag("option", { label= val }):tag("value"):text(tostring(val)):up():up();
					end
				end
				if field.options and value then
					form:tag("value"):text(value):up();
				end
			elseif field_type == "list-multi" then
				for _, val in ipairs(field.options or value) do
					if type(val) == "table" then
						form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up();
						if not field.options and val.default then
							form:tag("value"):text(val.value):up();
						end
					else
						form:tag("option", { label= val }):tag("value"):text(tostring(val)):up():up();
					end
				end
				if field.options and value then
					for _, val in ipairs(value) do
						form:tag("value"):text(val):up();
					end
				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 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
		else
			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

return {
	new = new;
};


--[=[

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 };
}


--]=]