Aller au contenu

Module:ArgumentContent

De Wikidébats, l'encyclopédie des débats et des arguments « pour » et « contre »

La documentation pour ce module peut être créée à Module:ArgumentContent/doc

-- Module:ArgumentContent (MediaWiki 1.43)

local p = {}

--	========================
--	I18N
--	========================
local MESSAGES = {
	fr = {
		justifs_title = "Justifications",
		objs_title = "Objections",
		edit = "modifier",
		edit_justifs_tt = "Modifier la liste des arguments ci-dessous",
		edit_objs_tt = "Modifier la liste des objections ci-dessous",
		add_argument = "Ajouter un argument",
		add_objection = "Ajouter une objection",
		add_justif_tt = "Ajouter une justification à l'argument : %s",
		add_obj_tt = "Ajouter une objection à l'argument : %s",
		none_argument = "''Aucun argument n'a été entré.''",
		none_objection = "''Aucune objection n'a été entrée.''",
		none_content = "''Aucun contenu n'a été entré.''",
		detailed_debate_title = "%s",
		prop_page_params = "Paramètres de la page", -- plus utilisé, conservé pour compat éventuelle
		prop_argument_content = "Contenu d'argument",
		prop_arguments_map = "Carte des arguments",
		-- Propriétés SMW utilisées pour la requête groupée
		prop_argument_number = "Numéro d'argument",
		prop_detailed_debate = "Débat détaillé",
		prop_justifs_list = "Liste des justifications",
		prop_objs_list = "Liste des objections",
		prop_warn_justifs = "Avertissements pour les justifications",
		prop_warn_objs = "Avertissements pour les objections",
		form_justifications = "Justifications",
		form_objections = "Objections",
		form_new_argument_title = "Nouveau titre d'argument",
		form_type = "type",
		type_justification = "Justification",
		type_objection = "Objection",
		template_banner = "Bandeau %s",
	},
	en = {
		justifs_title = "Supporting arguments",
		objs_title = "Objections",
		edit = "edit",
		edit_justifs_tt = "Edit the list of arguments below",
		edit_objs_tt = "Edit the list of objections below",
		add_argument = "Add an argument",
		add_objection = "Add an objection",
		add_justif_tt = "Add a supporting argument to: %s",
		add_obj_tt = "Add an objection to: %s",
		none_argument = "''No argument has been entered.''",
		none_objection = "''No objection has been entered.''",
		none_content = "''No content has been entered.''",
		detailed_debate_title = "%s",
		prop_page_params = "Page parameters",
		prop_argument_content = "Argument content",
		prop_arguments_map = "Arguments map",
		prop_argument_number = "Argument number",
		prop_detailed_debate = "Detailed debate",
		prop_justifs_list = "List of justifications",
		prop_objs_list = "List of objections",
		prop_warn_justifs = "Warnings for justifications",
		prop_warn_objs = "Warnings for objections",
		form_justifications = "Supporting arguments",
		form_objections = "Objections",
		form_new_argument_title = "New argument title",
		form_type = "type",
		type_justification = "Justification",
		type_objection = "Objection",
		template_banner = "%s banner",
	},
	es = {
		justifs_title = "Justificaciones",
		objs_title = "Objeciones",
		edit = "editar",
		edit_justifs_tt = "Editar la lista de argumentos de abajo",
		edit_objs_tt = "Editar la lista de objeciones de abajo",
		add_argument = "Añadir un argumento",
		add_objection = "Añadir una objeción",
		add_justif_tt = "Añadir una justificación a: %s",
		add_obj_tt = "Añadir una objeción a: %s",
		none_argument = "''No se ha introducido ningún argumento.''",
		none_objection = "''No se ha introducido ninguna objeción.''",
		none_content = "''No se ha introducido ningún contenido.''",
		detailed_debate_title = "%s",
		prop_page_params = "Parámetros de la página",
		prop_argument_content = "Contenido del argumento",
		prop_arguments_map = "Mapa de argumentos",
		prop_argument_number = "Número de argumento",
		prop_detailed_debate = "Debate detallado",
		prop_justifs_list = "Lista de justificaciones",
		prop_objs_list = "Lista de objeciones",
		prop_warn_justifs = "Avisos para las justificaciones",
		prop_warn_objs = "Avisos para las objeciones",
		form_justificaciones = "Justificaciones",
		form_objections = "Objeciones",
		form_new_argument_title = "Nuevo título de argumento",
		form_type = "tipo",
		type_justification = "Justificación",
		type_objection = "Objeción",
		template_banner = "Aviso %s",
	},
}

--	========================
--	Helpers
--	========================
local function trim(s)
	if type(s) ~= "string" then
		return s
	end
	return s:match("^%s*(.-)%s*$")
end

--	Fast split (plain find, sans patterns)
local function split(s, delim)
	if type(s) ~= "string" or s == "" then
		return {}
	end
	delim = delim or ","
	if delim == "" then
		return { s }
	end
	local out, start = {}, 1
	while true do
		local i, j = string.find(s, delim, start, true)
		if not i then
			out[#out+1] = trim(string.sub(s, start))
			break
		end
		out[#out+1] = trim(string.sub(s, start, i - 1))
		start = j + 1
	end
	return out
end

local function safeIndex(t, idx, default)
	if type(t) ~= "table" then
		return default
	end
	local v = t[idx]
	if v == nil or v == "" then
		return default
	end
	return v
end

local function toNumber(s, default)
	local n = tonumber(s or "")
	return n or default or 0
end

local function urlencode(s, mode)
	return mw.uri.encode(s or "", mode or "QUERY")
end

local function anchorencode(s)
	return mw.uri.anchorEncode(s or "")
end

--	========================
--	I18N helpers
--	========================
local function normalizeLang(code)
	code = trim(code or "")
	if code == "" then
		return ""
	end
	local base = code:match("^([a-zA-Z]+)") or code
	return mw.ustring.lower(base)
end

local function getLang(args)
	local param = normalizeLang(args.lang or args.language or "")
	if param ~= "" and MESSAGES[param] then
		return param
	end
	local contentLang = ""
	if mw.getContentLanguage then
		local obj = mw.getContentLanguage()
		if obj and obj.getCode then
			contentLang = normalizeLang(obj:getCode())
		end
	end
	if contentLang ~= "" and MESSAGES[contentLang] then
		return contentLang
	end
	return "en"
end

local function msg(lang, key, ...)
	local pack = MESSAGES[lang] or MESSAGES.en
	local text = pack[key] or (MESSAGES.en and MESSAGES.en[key]) or key
	if select("#", ...) > 0 then
		return string.format(text, ...)
	end
	return text
end

--	========================
--	VariablesLua cache
--	========================
local VL = (mw.ext and mw.ext.VariablesLua) or nil
local LOCAL_CACHE = {}

local function cache_get(key)
	local v = LOCAL_CACHE[key]
	if v ~= nil then
		return v
	end
	if VL and VL.var then
		local vv = VL.var(key, nil)
		if vv ~= nil and vv ~= "" then
			LOCAL_CACHE[key] = vv
			return vv
		end
	end
	return nil
end

local function cache_set(key, value)
	local v = value or ""
	LOCAL_CACHE[key] = v
	if VL and VL.vardefine then
		if VL.var and VL.var(key, nil) == v then
			return
		end
		VL.vardefine(key, v)
	end
end

--	========================
--	SMW access
--	========================
local function smwShow(frame, page, property)
	if not page or page == "" or not property or property == "" then
		return ""
	end
	local cacheKey = "WDB:pf#show|" .. page .. "|" .. property
	local cached = cache_get(cacheKey)
	if cached ~= nil then
		return cached
	end
	local out = trim(frame:callParserFunction("#show", { page, "?" .. property }) or "")
	cache_set(cacheKey, out)
	return out
end

--	========================
--	SMW – une seule requête pour toutes les propriétés d'un argument
--	========================

local ARGDATA_CACHE = {}

local function smwGetArgumentData(frame, pageTitle)
	if not pageTitle or pageTitle == "" then
		return {}
	end

	local cacheKey = "WDB:argDataByPage|" .. pageTitle
	local cached = ARGDATA_CACHE[cacheKey]
	if cached then
		return cached
	end

	-- Propriétés SMW (noms EXACTS des Property:)
	local PROP_ID			= "Numéro d'argument"
	local PROP_CONTENT		= "Contenu d'argument"
	local PROP_JUSTIFS		= "Liste des justifications"
	local PROP_OBJS			= "Liste des objections"
	local PROP_DETAILED		= "Débat détaillé"
	local PROP_WARN_J		= "Avertissements pour les justifications"
	local PROP_WARN_O		= "Avertissements pour les objections"

	local data = {
		pageTitle			= pageTitle,
		idArgument			= "",
		contenuArgument		= "",
		pageDebatDetaille	= "",
		pairesJustifsRaw	= "",
		pairesObjRaw		= "",
		bandeauxJustifsCsv	= "",
		bandeauxObjsCsv		= "",
	}

	if not (mw.smw and mw.smw.ask) then
		ARGDATA_CACHE[cacheKey] = data
		return data
	end

	local query = {
		"[[ " .. pageTitle .. " ]]",
		"?" .. PROP_ID,
		"?" .. PROP_CONTENT,
		"?" .. PROP_JUSTIFS,
		"?" .. PROP_OBJS,
		"?" .. PROP_DETAILED,
		"?" .. PROP_WARN_J,
		"?" .. PROP_WARN_O,
		"mainlabel=page",
		"limit=1",
	}

	local res = mw.smw.ask(query)
	local row = res and res[1] or nil
	if not row then
		ARGDATA_CACHE[cacheKey] = data
		return data
	end

	-- Nettoie un éventuel lien [[Page]] ou [[:Page|Texte]]
	local function normalizePageValue(v)
		v = trim(tostring(v or ""))
		-- enlève crochets et éventuel deux-points + texte après |
		v = v:gsub("^%[%[:?(.-)%]%]$", "%1")
		v = v:gsub("^(.-)|.*$", "%1")
		return trim(v)
	end

	local function firstField(r, key, isPage)
		local v = r[key]
		if v == nil then
			return ""
		end
		if type(v) == "table" then
			v = v[1]
		end
		v = trim(tostring(v or ""))
		if isPage then
			v = normalizePageValue(v)
		end
		return v
	end

	data.idArgument			= firstField(row, PROP_ID, false)
	data.contenuArgument	= firstField(row, PROP_CONTENT, false)
	data.pageDebatDetaille	= firstField(row, PROP_DETAILED, true)	-- ✨ page → on nettoie [[...]]
	data.pairesJustifsRaw	= firstField(row, PROP_JUSTIFS, false)
	data.pairesObjRaw		= firstField(row, PROP_OBJS, false)
	data.bandeauxJustifsCsv	= firstField(row, PROP_WARN_J, false)
	data.bandeauxObjsCsv	= firstField(row, PROP_WARN_O, false)

	ARGDATA_CACHE[cacheKey] = data
	return data
end

--	========================
--	Page Forms helpers
--	========================
local function formlink(frame, form, target, linktext, tooltip)
	return frame:callParserFunction("#formlink", {
		"form=" .. (form or ""),
		"target=" .. (target or ""),
		"link text=" .. (linktext or ""),
		"tooltip=" .. (tooltip or "")
	})
end

local function queryformlink(frame, form, querystring, linktype, linktext, tooltip)
	return frame:callParserFunction("#queryformlink", {
		"form=" .. (form or ""),
		"query string=" .. (querystring or ""),
		"link type=" .. (linktype or ""),
		"link text=" .. (linktext or ""),
		"tooltip=" .. (tooltip or "")
	})
end

--	========================
--	Bandeaux (mémoïsés)
--	========================
local BANDEAUX_CACHE = {}
local function expandBandeaux(frame, csv, lang)
	if not csv or csv == "" then
		return ""
	end
	local ck = "WDB:bandeaux|" .. lang .. "|" .. csv
	local c = BANDEAUX_CACHE[ck]
	if c ~= nil then
		return c
	end
	local html = {}
	for _, item in ipairs(split(csv, ",")) do
		if item and item ~= "" then
			local title = msg(lang, "template_banner", item)
			html[#html+1] = frame:expandTemplate{
				title = title,
				args = { item }
			}
		end
	end
	local out = table.concat(html, " ")
	BANDEAUX_CACHE[ck] = out
	return out
end

function p.render(frame)
	local parent = frame:getParent()
	local args = {}

	--	Fallback depuis parent
	for k, v in pairs(parent and parent.args or {}) do
		args[k] = v
	end
	--	Override par #invoke
	for k, v in pairs(frame.args or {}) do
		if v ~= nil and v ~= "" then
			args[k] = v
		end
	end

	local L = getLang(args)

	local nowiki = mw.text.nowiki
	local uencQ = function(s) return mw.uri.encode(s or "", "QUERY") end
	local aenc = function(s) return mw.uri.anchorEncode(s or "") end

	-- Messages
	local PROP_ARG_CONTENT = msg(L, "prop_argument_content")
	local PROP_ARGS_MAP    = msg(L, "prop_arguments_map")
	local TITLE_JUSTIFS    = msg(L, "justifs_title")
	local TITLE_OBJS       = msg(L, "objs_title")
	local NONE_ARG         = msg(L, "none_argument")
	local NONE_OBJ         = msg(L, "none_objection")
	local NONE_CONTENT     = msg(L, "none_content")
	local DETAILED_TITLE   = function(s) return msg(L, "detailed_debate_title", s) end
	local EDIT             = msg(L, "edit")
	local EDIT_J_TT        = msg(L, "edit_justifs_tt")
	local EDIT_O_TT        = msg(L, "edit_objs_tt")
	local ADD_ARG          = msg(L, "add_argument")
	local ADD_OBJ          = msg(L, "add_objection")
	local ADD_J_TT         = function(s) return msg(L, "add_justif_tt", s) end
	local ADD_O_TT         = function(s) return msg(L, "add_obj_tt", s) end

	--	Paramètres
	local pageArgument = mw.uri.decode(trim(args.argument or ""))
	local cheminRaw = trim(args.path or args.chemin or "")
	local levelRaw = toNumber(args.level or args.niveau, 0)
	local niveau = levelRaw + 1
	local typeArg = trim(args.type or "")
	local avertissements = trim(args.warnings or args.avertissements or "")
	local pageParam = trim(args.page or "")
	local editableRaw = mw.ustring.lower(trim(args.editable or ""))
	local isEditable = (editableRaw == "yes" or editableRaw == "oui" or editableRaw == "true" or editableRaw == "1")

	local cheminParts = split(cheminRaw, "@@@")
	local lastPath = safeIndex(cheminParts, #cheminParts, "")
	local racineParts = split(lastPath, ":::")
	local racine = safeIndex(racineParts, 2, "")

	--	UNE SEULE requête SMW
	local data = smwGetArgumentData(frame, pageArgument)

	local pageTitle			= data.pageTitle or pageArgument
	local idArgument		= data.idArgument or ""
	local contenuArgument	= data.contenuArgument or ""
	local pageDebatDetaille	= data.pageDebatDetaille or ""
	local pairesJustifsRaw	= data.pairesJustifsRaw or ""
	local pairesObjRaw		= data.pairesObjRaw or ""
	local bandeauxJustifsCsv= data.bandeauxJustifsCsv or ""
	local bandeauxObjsCsv	= data.bandeauxObjsCsv or ""

	--	Si aucun contenu du tout
	if (contenuArgument == "" or contenuArgument == nil)
		and (pairesJustifsRaw == "" or pairesJustifsRaw == nil)
		and (pairesObjRaw == "" or pairesObjRaw == nil)
		and (pageDebatDetaille == "" or pageDebatDetaille == nil)
	then
		return '<div class="aucun-contenu">' .. NONE_CONTENT .. '</div>__NOTOC__'
	end

	local avertHtml = ""
	if avertissements and avertissements ~= "" then
		avertHtml = expandBandeaux(frame, avertissements, L)
	end

	local preOpen, preClose = "", ""
	if isEditable and levelRaw == 1 then
		preOpen = string.format('<div class="contenu-argument-%s">', typeArg or "")
		preClose = '</div>'
	end

	local bodyHtml = {}
	bodyHtml[#bodyHtml+1] = string.format(
		'<div class="contenu-argument">%s</div>',
		contenuArgument or ""
	)

	--	Débat détaillé
	if pageDebatDetaille and pageDebatDetaille ~= "" then
		local carteProp = PROP_ARGS_MAP
		local carteArgs = smwShow(frame, pageDebatDetaille, carteProp)
		if (carteArgs == "" or carteArgs == nil) and L ~= "fr" then
			carteArgs = smwShow(frame, pageDebatDetaille, MESSAGES.fr.prop_arguments_map)
		end
		bodyHtml[#bodyHtml+1] = string.format(
			'<div class="carte-debat-detaille onglet-externe">' ..
				'<div class="titre-debat-detaille">%s</div>' ..
				'%s' ..
			'</div>',
			DETAILED_TITLE(pageDebatDetaille),
			carteArgs or ""
		)

	--	Sinon : Justifs + Objs
	else
		local function buildList(pairesRaw, sens, sensInverseOpt)
			local out = {}
			local paires = split(pairesRaw, "&&&")
			local total = #paires
			local sensInverse = sensInverseOpt
			local typePour = (typeArg == "pour")

			for i = 1, total do
				local brut = safeIndex(paires, i, "")
				if brut ~= "" then
					local paire = split(brut, "-¡-")
					local id = safeIndex(paire, 1, "")
					local titre = safeIndex(paire, 2, id)

					if id ~= "" or titre ~= "" then
						local titreEnc = uencQ(titre)
						local idEnc = uencQ(id)
						local t = sens
						if sensInverse then
							t = typePour and "contre" or "pour"
						end

						out[#out+1] =
							string.format(
								'<div class="argument-expandable">' ..
									'<div id="%s" class="argument argument-expandable-title level-%d level-sup" ' ..
										'data-template="%s" ' ..
										'data-page="%s" ' ..
										'data-argument="%s" ' ..
										'data-type="%s" ' ..
										'data-level="%d" ' ..
										'data-root="%s" ' ..
										'data-path="%s@@@%s:::%s" ' ..
										'data-warnings="%s">' ..
										'[[File: Expand.svg | 11px | link= | alt= | class=fleche-deroulante]]' ..
										'[[File: Argument-%s.svg | 17px | link= | alt= | class=pictogramme-h4 mw-no-invert]]' ..
										'<span>%s</span>' ..
									'</div>' ..
									'<div class="argument-content-wrapper"></div>' ..
								'</div>',
								aenc(titre),
								niveau,
								PROP_ARG_CONTENT,
								nowiki(pageParam or ""),
								idEnc,
								nowiki(t),
								niveau,
								nowiki(racine),
								nowiki(cheminRaw or ""),
								nowiki(t),
								titreEnc,
								nowiki(avertissements or ""),
								nowiki(t),
								nowiki(titre)
							)
					end
				end
			end

			if #out == 0 then
				return '<div class="aucun-argument">' .. (sensInverse and NONE_OBJ or NONE_ARG) .. "</div>"
			end
			return table.concat(out, "")
		end

		local formJust = msg(L, "form_justifications")
		local formObj = msg(L, "form_objections")
		local formNew = msg(L, "form_new_argument_title")
		local formType = msg(L, "form_type")

		if L ~= "fr" then
			formJust = formJust ~= "" and formJust or MESSAGES.fr.form_justifications
			formObj = formObj ~= "" and formObj or MESSAGES.fr.form_objections
			formNew = formNew ~= "" and formNew or MESSAGES.fr.form_new_argument_title
			formType = formType ~= "" and formType or MESSAGES.fr.form_type
		end

		local modJust = ""
		local modObj = ""
		local btnJust = ""
		local btnObj = ""

		if isEditable then
			modJust = formlink(frame, formJust, pageTitle, EDIT, EDIT_J_TT)
			modObj = formlink(frame, formObj, pageTitle, EDIT, EDIT_O_TT)

			local typeJust = msg(L, "type_justification")
			local typeObj = msg(L, "type_objection")
			if L ~= "fr" then
				typeJust = typeJust ~= "" and typeJust or MESSAGES.fr.type_justification
				typeObj = typeObj ~= "" and typeObj or MESSAGES.fr.type_objection
			end

			btnJust = queryformlink(
				frame,
				formNew,
				string.format(
					"%s[%s]=%s&%s[ID]=%s&_run",
					formNew,
					formType,
					(typeJust or ""),
					formNew,
					(idArgument or "")
				),
				"post button",
				ADD_ARG,
				ADD_J_TT(pageArgument or "")
			)

			btnObj = queryformlink(
				frame,
				formNew,
				string.format(
					"%s[%s]=%s&%s[ID]=%s&_run",
					formNew,
					formType,
					(typeObj or ""),
					formNew,
					(idArgument or "")
				),
				"post button",
				ADD_OBJ,
				ADD_O_TT(pageArgument or "")
			)
		end

		local gauche = string.format(
			'<div class="colonne-gauche">' ..
				'<div class="NavFramex %s">' ..
					'<div class="NavHead"><span class="titre-boite">%s</span>%s</div>' ..
					'<div class="NavContent">' ..
						'<div>%s</div>' ..
						'%s' ..
					'</div>' ..
					'%s' ..
				'</div>' ..
			'</div>',
			(typeArg == "pour") and "bordure-argument-pour" or "bordure-argument-contre",
			TITLE_JUSTIFS,
			(isEditable and ('<span class="modifier-section">' .. modJust .. '</span>') or ''),
			expandBandeaux(frame, bandeauxJustifsCsv, L),
			(pairesJustifsRaw ~= "" and buildList(pairesJustifsRaw, typeArg, false) or '<div class="aucun-argument">' .. NONE_ARG .. '</div>'),
			(isEditable and ('<div class="NavButton"><div class="bouton-ajouter navigation-not-searchable">' .. btnJust .. '</div></div>') or '')
		)

		local droite = string.format(
			'<div class="colonne-droite">' ..
				'<div class="NavFramex %s">' ..
					'<div class="NavHead"><span class="titre-boite">%s</span>%s</div>' ..
					'<div class="NavContent">' ..
						'<div>%s</div>' ..
						'%s' ..
					'</div>' ..
					'%s' ..
				'</div>' ..
			'</div>',
			(typeArg == "pour") and "bordure-argument-contre" or "bordure-argument-pour",
			TITLE_OBJS,
			(isEditable and ('<span class="modifier-section">' .. modObj .. '</span>') or ''),
			expandBandeaux(frame, bandeauxObjsCsv, L),
			(pairesObjRaw ~= "" and buildList(pairesObjRaw, typeArg, true) or '<div class="aucun-argument">' .. NONE_OBJ .. '</div>'),
			(isEditable and ('<div class="NavButton"><div class="bouton-ajouter navigation-not-searchable">' .. btnObj .. '</div></div>') or '')
		)

		bodyHtml[#bodyHtml+1] = '<div class="colonnes">'
		bodyHtml[#bodyHtml+1] = gauche
		bodyHtml[#bodyHtml+1] = droite
		bodyHtml[#bodyHtml+1] = '</div>'
	end

	local html = {}
	if avertHtml ~= "" then
		html[#html+1] = string.format('<div>%s</div>', avertHtml)
	end
	html[#html+1] = preOpen
	html[#html+1] = table.concat(bodyHtml, "")
	html[#html+1] = preClose
	html[#html+1] = "__NOTOC__"

	return table.concat(html, "")
end

return p