Questa pagina è protetta dallo spostamento
Questa pagina è protetta

Modulo:Collegamenti esterni

Da Wikipedia, l'enciclopedia libera.
Jump to navigation Jump to search

Modulo che implementa il template {{Collegamenti esterni}}.

Ha una sottopagina di configurazione per ciascun gruppo di collegamenti esterni:

Tipo di proprietà

Quasi tutti i collegamenti si basano su proprietà di tipo "identificativo esterno", quindi per ciascuno bisognerà inserire, nel relativo gruppo della sottopagina di configurazione, almeno l'ID della proprietà Wikidata ("pid") e l'URL di formattazione ("url"). Per i rari casi di utilizzo di proprietà di tipo "URL", al posto dell'URL di formattazione va specificato il titolo del collegamento ("titolo").

Ordine collegamenti

All'interno di ciascun gruppo i collegamenti sono visualizzati nell'ordine in cui sono stati inseriti nella pagina di configurazione (ovviamente se è presente la relativa proprietà Wikidata). Per cambiare l'ordine è quindi sufficiente cambiare l'ordine dei collegamenti nelle sottopagine di configurazione.

Disambiguare i collegamenti relativi allo stesso sito web

Quando più proprietà Wikidata si riferiscono allo stesso sito web (esempio per le diverse schede di un calciatore, allenatore, dirigente, ...) e possono essere usate contemporaneamente in uno stesso elemento Wikidata, ci sono tre modi per disambiguare i collegamenti generati:

  1. differenziarli con "tipo", esempio nel primo tipo = 'calciatore' e nel secondo tipo = 'allenatore'. La disambiguazione apparirà però sempre, anche quando presente una sola proprietà nell'elemento.
  2. differenziarli con "titolo", esempio nel primo titolo = 'Scheda giocatore di $1' e nel secondo titolo = 'Scheda allenatore di $1'. La disambiguazione apparirà però sempre, anche quando presente una sola proprietà nell'elemento, inoltre la frase apparirà in corsivo anche se non fosse effettivamente il titolo della pagina.
  3. differenziarli con "sitodis", esempio nel primo sitodis = 'giocatore' e nel secondo sitodis = 'allenatore'. In questo modo, rispetto ai due metodi precedenti: (1) la disambiguazione apparirà automaticamente solo quando è presente più di una proprietà che utilizza lo stesso sito web e (2) si potranno omettere titolo e sito, lasciandoli al loro valore predefinito, ossia il titolo della pagina e il dominio dell'URL.
Controllo duplicati

Verifica automatica di proprietà duplicate: P6999, P6941, P6862, P648.


--[[
* Modulo che implementa il template Collegamenti esterni.
]]--

require('Modulo:No globals')

local getArgs = require('Modulo:Arguments').getArgs
local mWikidata = require('Modulo:Wikidata')
local mCitazione = require('Modulo:Citazione')
local mEditAtWikidata = require('Modulo:Modifica su Wikidata')
-- Permette di definire l'ordine di visualizzazione dei vari gruppi
local orderedGroupNames = {
	'Ufficiali', 'Enciclopedie', 'Biografie', 'Testi',
	'Letteratura', 'Politica', 'Religione', 'Architettura',
	'Astronomia', 'Biologia', 'Editoria', 'Geografia',
	'Linguistica', 'MAB', 'Software', 'Calcio', 'Sci',
	'Sport', 'Videogiochi', 'Musica', 'Fumetti', 'Cinema'
}
-- Soglie di attenzione sul numero elevato di collegamenti, per categorie di controllo
local MOLTI_LINK = 12
local MOLTI_LINK_2 = 15
-- Categorie di servizio
local catLetta = ' letta da Wikidata'
local catMultiSenzaQual = ' multipla letta da Wikidata senza qualificatore'
local catMoltiLink = 'Voci con template Collegamenti esterni e molti collegamenti'
local catMoltiLink2 = 'Voci con template Collegamenti esterni e molti collegamenti (soglia maggiore)'
local catEmpty = 'Voci con template Collegamenti esterni senza dati da Wikidata'
local catUnknownQual = 'Voci con template Collegamenti esterni e qualificatori sconosciuti'
local catExcessiveLoad = 'Voci con template Collegamenti esterni e molte entità Wikidata caricate'

-- =============================================================================
--                            Funzioni di utilità
-- =============================================================================

-- Restituisce la configurazione delle sottopagine.
--
-- @return {table}
local function readConfig()
	local ret = {}
	for _, groupName in ipairs(orderedGroupNames) do
		ret[groupName] = mw.loadData('Modulo:Collegamenti esterni/' .. groupName)
	end
	return ret
end

-- Restituisce il titolo della pagina corrente rimuovendo eventuale testo tra parentesi.
-- Se l'etichetta dell'elemento Wikidata contiene delle parentesi,
-- non le rimuove perché significa che fanno parte del titolo.
-- Con il parametro "from" (elemento Wikidata arbitrario) usa sempre l'etichetta.
--
-- @param {string} from
-- @return {string}
local function getCurrentTitle(from)
	local ret
	local label = mWikidata._getLabel({ from })
	if from then
		ret = label
	else
		ret = mw.title.getCurrentTitle().text
		if not (label and string.find(label, ' %(')) then
			ret = mw.text.split(ret, ' %(')[1]
		end
	end
	return ret
end

-- Restituisce il dominio dell'URL specificato.
--
-- @param {string} url
-- @return {string}
local function getDomain(url)
	return mw.uri.new(url).host:gsub('^www.', '')
end

-- Restituisce true se l'elemento collegato alla pagina o quello specificato in from
-- ha tra i valori (o loro sottoclassi) della proprietà indicata uno degli elementi specificati.
-- Restituisce come secondo valore una tabella con gli ID delle entità caricate ai fini della ricerca.
-- @param {string} prop - codice della proprietà 'Pxxx'
-- @param {table} [entityIds] - array dei valori (strighe 'Qxxx') che può avere
-- @param {string} from
-- @return {boolean}
-- @return {table}
local function checkEntity(prop, entityIds, from)
	local args = { from = from, recursion = 8 }
	for _, entityId in ipairs(entityIds) do
		table.insert(args, entityId)
	end
	return mWikidata._propertyHasEntity(prop, args)
end

-- Converte un parametro ricevuto come 'Xxx, yyy' in { 'Xxx'=true, 'Yyy'=true }
local function ParametroElenco(param)
	local chiavi = {}
	local valori = param and mw.text.split(param, ',')
	if valori then
		for _, str in ipairs(valori) do
			chiavi[mw.getContentLanguage():ucfirst(mw.text.trim(str))] = true
		end
	end
	return chiavi
end

-- =============================================================================
--                            Classe ExtLink
-- =============================================================================

-- La classe ExtLink rappresenta un singolo collegamento esterno.
-- Al suo interno ha un riferimento alla propria configurazione (linkConf)
-- e nel caso la proprietà Wikidata abbia più valori li raccoglie tutti.

local ExtLink = {}

-- Costruttore della classe ExtLink.
--
-- @param {table} [url] - uno o più URL, quanti sono i valori della proprietà Wikidata
-- @param {table} [qualifier] - eventuali qualificatori da annettere al titolo per ciascun URL
-- @param {table} linkConf - la configurazione fissa per questo collegamento esterno
-- @param {table} extraConf - altri elementi di configurazione ricavati dall'item
-- @param {string} from - entityId se diverso da quello collegato alla pagina corrente
-- @return {table} un nuovo oggetto ExtLink
function ExtLink:new(url, qualifier, linkConf, extraConf, from)
	local self = {}
	setmetatable(self, { __index = ExtLink })

	-- sostituisce eventuali spazi con %20
	for i = 1, #url do
		url[i] = url[i]:gsub(' ', '%%20')
	end
	self.url = url
	self.qualifier = #url > 1 and qualifier
	self.linkConf = linkConf
	self.extraConf = extraConf
	self.from = from
	self.title = getCurrentTitle()
	self.title = self.from and mWikidata._getLabel({ self.from }) or self.title
	self.extraConf.medium = self.linkConf.medium or 'web'
	
	return self
end

-- Restituisce il parametro titolo per il modulo Citazione.
--
-- @return {string}
function ExtLink:_getTitolo()
	local titolo = self.extraConf.titolo[1] or self.title
	if self.linkConf.titolo then
		titolo = self.linkConf.titolo:gsub('$1', titolo)
	end
	if self.qualifier and self.qualifier[1] ~= nil then
		return string.format('%s (%s)', titolo, self.qualifier[1])
	else
		return titolo
	end
end

-- Restituisce il parametro altrilink per il modulo Citazione.
-- Nel caso di valori multipli, genera quelli successivi al primo.
--
-- @return {table}
function ExtLink:_getAltriLink()
	local tbl = {}
	local titolo, specifica
	for i = 2, #self.url do
		titolo = self.extraConf.titolo[i] or self.title
		if self.qualifier and self.qualifier[i] ~= nil then
			specifica = self.qualifier[i]
		else
			local ripetuto = false -- controllo se stesso titolo già usato
			for j = 1, i - 1 do
				if titolo == (self.extraConf.titolo[j] or self.title) then 
					ripetuto = true
					break
				end
			end
			if ripetuto then
				specifica = 'altra versione'
			else
				specifica = nil -- se titoli diversi, va bene anche senza specificazione
			end
		end
		if specifica then
			titolo = string.format('%s (%s)', titolo, specifica)
		end
		table.insert(tbl, { self.url[i], titolo })
	end
	return tbl
end

-- Formatta il collegamento esterno quando la proprietà è di tipo URL.
--
-- @return {string}
function ExtLink:_formatPropertyURL()
	local formattedLinks = {}
	local currTitle = getCurrentTitle(self.from)
	local claims = mWikidata._getClaims(self.linkConf.pid, { from = self.from }) or {}
	for idx, claim in ipairs(claims) do
		local langs = mWikidata._formatQualifiers(claim, 'P407', { formatting = 'raw' }, true)
		langs = (#langs == 1 and langs[1] == 'Q652') and {} or langs
		for i, lang in ipairs(langs) do
			langs[i] = mWikidata._getLabel({ lang })
		end
		local formattedLink = mCitazione.cita_da_modulo(
			self.extraConf.medium,
			{
				url = mWikidata._formatStatement(claim),
				titolo = self.linkConf.titolo:gsub('$1', currTitle),
				lingua = table.concat(langs, ','),
				cid = self.linkConf.cid,
				urlarchivio = self.extraConf.archivio[idx],
				dataarchivio = self.extraConf.dataarch[idx],
				urlmorto = self.extraConf.urlmorto[idx] and 'sì' or (self.extraConf.archivio[idx] and 'no')
			})
		table.insert(formattedLinks, '* ' .. formattedLink ..
					 mEditAtWikidata._showMessage({ pid = self.linkConf.pid, qid = self.from }))
	end
	return table.concat(formattedLinks, '\n')
end

-- Formatta il collegamento esterno come elemento di un elenco puntato.
--
-- @return {string}
function ExtLink:getListItem()
	-- se è specificato l'URL di formattazione è una
	-- proprietà di tipo "identificativo esterno" altrimenti di tipo URL
	if self.linkConf.url then
		return '* ' .. mCitazione.cita_da_modulo(
			self.extraConf.medium,
			{
				url = self.url[1],
				titolo = self:_getTitolo(),
				altrilink = self:_getAltriLink(),
				sito = self.linkConf.opera and '' or self.extraConf.sito,
				opera = self.linkConf.opera,
				editore = self.linkConf.editore,
				lingua = self.linkConf.lingua,
				cid = self.linkConf.cid,
				autore = self.linkConf.autore or self.extraConf.autore,
				volume = self.extraConf.volume,
				p = self.extraConf.pagina,
				data = self.linkConf.data or self.extraConf.data,
				tipo = self.linkConf.tipo or self.sitodis
			}) .. mEditAtWikidata._showMessage({ pid = self.linkConf.pid, qid = self.from })
	else
		return self:_formatPropertyURL()
	end
end

-- =============================================================================
--                            Classe LinksManager
-- =============================================================================

-- La classe LinksManager è la classe principale del modulo.
-- Al suo interno ha un riferimento a tutti collegamenti esterni (extLinks)
-- presenti in un dato elemento Wikidata e li può restituire tutti formattati 
-- nel modo più appropriato.

local LinksManager = {}

-- Costruttore della classe LinksManager.
--
-- @param {table} args
-- @return {table} un nuovo oggetto LinksManager
function LinksManager:new(args)
	local self = {}
	setmetatable(self, { __index = LinksManager })

	self.numExtLinks = 0
	self.categories = {}
	self.catColon = ''
	self.from = args.from
	self.escludi = ParametroElenco(args.escludi)
	self.soloprop = ParametroElenco(args.prop)
	self.sologruppi = ParametroElenco(args.gruppo)
	self.solomedium = ParametroElenco(args.medium)
	-- la pagina dei test utilizza uno stub del modulo Wikidata
	if mw.title.getCurrentTitle().prefixedText ==
	   'Discussioni modulo:Collegamenti esterni/test' then
		self:_setStubWikidata(args.stubwd)
	end
	self.extLinks = self:_getExtLinks()

	return self
end

-- Permette di specificare uno stub del modulo Wikidata
-- in modo da ottenere i valori delle proprietà in modo deterministico,
-- non dipendenti da modifiche utente a un elemento Wikidata.
--
-- @param {table} stubwd
function LinksManager:_setStubWikidata(stubwd)
	mEditAtWikidata = { _showMessage = function(frame) return '' end }
	mWikidata = stubwd
	self.catColon = ':'
	self.debug = true
end

-- Controlla se un elemento fa parte delle esclusioni richieste dall'utente
function LinksManager:_Escluso(elemento, inclusi)
	return self.escludi[elemento] or (next(inclusi) and inclusi[elemento] ~= true)
end

-- Ottiene tutti i collegamenti esterni (configurati) presenti in un dato elemento Wikidata
-- suddivisi per gruppo.
--
-- @return {table}
function LinksManager:_getExtLinks()
	local ret, groupSites = {}, {}
	local cfg = readConfig()
	local loadedEntities = setmetatable({}, {
		__newindex = function(t1, key, t2)
			if not t2 then return end
			for k, v in pairs(t2) do rawset(t1, k, v) end
		end })
	for _, groupName in ipairs(orderedGroupNames) do -- Per ogni gruppo tematico
		groupSites[groupName] = {}
		ret[groupName] = {}
		-- controlla se è un gruppo escluso manualmente dall'utente
		if self:_Escluso(groupName, self.sologruppi) then
			cfg[groupName] = {}
		end
		for _, linkConf in ipairs(cfg[groupName]) do -- Per ogni sito configurato
			local claims, valido
			-- se il sito non è escluso manualmente dall'utente, avviene la lettura da Wikidata
			if not (self:_Escluso(linkConf.pid, self.soloprop) or self:_Escluso(mw.getContentLanguage():ucfirst(linkConf.medium or 'web'), self.solomedium)) then
				claims = mWikidata._getClaims(linkConf.pid, { from = self.from })
			end
			-- controlla se ci sono valori
			if claims and #claims > 0 then
				-- controlla se è un sito da escludere per soggetto non pertinente
				if type(linkConf.vincolo) == 'table' then
					local Nvincoli = 0
					for _ in ipairs(linkConf.vincolo) do Nvincoli = Nvincoli + 1 end
					for i = 2, Nvincoli, 2 do -- Per ogni coppia 'proprietà', {valori}
						valido, loadedEntities[i] = checkEntity(linkConf.vincolo[i - 1], linkConf.vincolo[i], self.from)
						if valido then break end
					end
				else
					valido = true
				end
			end
			-- se il sito è abilitato, viene aggiunto a quelli da mostrare
			if valido then
				local url, qualifier, extraConf
				local Nqualtitoli = 0
				-- Qualificatori generali
				-- In caso di valori multipli, il titolo viene letto per tutti; gli altri qualificatori solo per il primo (altrimenti bisogna rivedere la formattazione dei valori multipli)
				-- Ricordarsi di elencare i qual. generali anche in altriQualNoti
				extraConf = {}
				extraConf.autore = mWikidata._formatQualifiers(claims[1], 'P50') or mWikidata._formatQualifiers(claims[1], 'P2093') -- come item o stringa
				extraConf.volume = mWikidata._formatQualifiers(claims[1], 'P478')
				extraConf.pagina = mWikidata._formatQualifiers(claims[1], 'P304')
				extraConf.data = mWikidata._formatQualifiers(claims[1], 'P577')
				extraConf.titolo = {}
				extraConf.archivio = {}
				extraConf.dataarch = {}
				extraConf.urlmorto = {}
				for i, claim in ipairs(claims) do
					extraConf.titolo[i] = mWikidata._formatQualifiers(claim, 'P1476') or mWikidata._formatQualifiers(claim, 'P1810') or mWikidata._formatQualifiers(claim, 'P1932') or mWikidata._formatQualifiers(claim, 'P742') -- titolo o "indicato come" o "riferito come" o pseudonimo
					if extraConf.titolo[i] then Nqualtitoli = Nqualtitoli + 1 end
					-- url archiviato, solo per le proprietà di tipo URL
					extraConf.archivio[i] = mWikidata._formatQualifiers(claim, 'P1065')
					extraConf.dataarch[i] = mWikidata._formatQualifiers(claim, 'P2960')
					extraConf.urlmorto[i] = mWikidata._formatQualifiers(claim, 'P582') or mWikidata._formatQualifiers(claim, 'P582', { snaktype = 'somevalue' })
				end
				-- Uno o più url ed eventuali qualificatori per distinguerli
				url = {}
				for i, claim in ipairs(claims) do
					if claim.qualifiers then
						local qualifierIds = ParametroElenco(linkConf.multi)
						for qualifierId, _ in pairs(qualifierIds) do
							if claim.qualifiers[qualifierId] then
								local formattedQualifier = mWikidata._formatQualifiers(claim, qualifierId, { nq = '1', formatting = 'raw' })
								if formattedQualifier then
									if not qualifier then qualifier = {} end
									qualifier[i] = mw.wikibase.getLabel(formattedQualifier)
									break
								end
							end
						end
						local altriQualNoti = {P407 = true, P50 = true, P2093 = true, P1476 = true, P1810 = true, P1932 = true, P742 = true, P577 = true, P478 = true, P304 = true, P813 = true, P1065 = true, P2960 = true, P582 = true}
						for qualifierId in pairs(claim.qualifiers) do
							if qualifierIds[qualifierId] ~= true and altriQualNoti[qualifierId] ~= true then
								table.insert(self.categories, string.format('[[%sCategoria:%s]]', self.catColon, catUnknownQual))
								break
							end
						end
					end
					-- crea l'url
					claim = mWikidata._formatStatement(claim)
					if linkConf.url then
						-- se proprietà di tipo id, il valore viene sotituito a "$1"
						claim = mw.message.newRawMessage(linkConf.url, claim):plain()
					end
					table.insert(url, claim)
				end
				-- nome sito, di default il dominio estratto dall'url
				extraConf.sito = linkConf.sito or getDomain(linkConf.url)
				-- Creazione dell'oggetto collegamento esterno, con l'url (o gli url) e gli altri dati raccolti
				table.insert(ret[groupName], ExtLink:new(url, qualifier, linkConf, extraConf, self.from))
				-- categoria per proprietà letta; se multipla e indistinguibile, usa categoria di avviso
				local tail = #url > 1 and linkConf.url and (qualifier == nil or #qualifier ~= #url) and (Nqualtitoli < #url - 1) and
						catMultiSenzaQual or catLetta
				table.insert(self.categories, string.format('[[%sCategoria:%s%s]]', self.catColon, linkConf.pid, tail))
				-- per verificare se un sito è ripetuto nel gruppo
				groupSites[groupName][extraConf.sito] = (groupSites[groupName][extraConf.sito] or 0) + 1
				-- conteggio complessivo dei collegamenti
				self.numExtLinks = self.numExtLinks + 1
			end
		end
	end
	-- verifica se un sito è ripetuto nel gruppo
	for _, groupName in ipairs(orderedGroupNames) do
		for _, extLink in ipairs(ret[groupName]) do
			-- necessaria la disambiguazione (più URL con lo stesso dominio nel gruppo),
			-- se configurata tramite "sitodis" nella configurazione.
			if groupSites[groupName][extLink.extraConf.sito] > 1 then
				extLink.sitodis = extLink.linkConf.sitodis
			end
		end
	end
	-- categoria di servizio sul numero di link
	local catnumero
	if self.numExtLinks == 0 then
		catnumero = catEmpty
	elseif self.numExtLinks > MOLTI_LINK then
		if self.numExtLinks > MOLTI_LINK_2 then catnumero = catMoltiLink2
		else catnumero = catMoltiLink end
	end
	if catnumero then
		table.insert(self.categories, string.format('[[%sCategoria:%s]]', self.catColon, catnumero))
	end
	-- categoria di servizio sul numero di entità caricate
	if #loadedEntities > 100 then
		table.insert(self.categories, string.format('[[%sCategoria:%s]]', self.catColon, catExcessiveLoad))
	end
	return ret
end

-- Formatta i collegamenti esterni come elenco puntato.
--
-- @param {table} [groupNames]
-- @return {string}
function LinksManager:_formatList(groupNames)
	local formattedLinks = {}
	for _, groupName in ipairs(groupNames) do
		for _, extLink in ipairs(self.extLinks[groupName]) do
			table.insert(formattedLinks, extLink:getListItem())
		end
	end
	return table.concat(formattedLinks, '\n')
end

-- Restituisce tutti i collegamenti esterni formattandoli come elenco puntato
--
-- @return {string}
function LinksManager:getList()
	local categories, links
	-- categorie di servizio
	categories = (mw.title.getCurrentTitle().namespace == 0 or self.debug) and
				 table.concat(self.categories) or ''
	-- collegamenti
	links = self:_formatList(orderedGroupNames)
	return links .. categories
end

-- =============================================================================
--                            Funzioni esportate
-- =============================================================================

local p = {}

-- Funzioni di utilità per il manuale, danno la soglia di attenzione sul n° di link.
function p.threshold(frame)
	return MOLTI_LINK
end
function p.threshold2(frame)
	return MOLTI_LINK_2
end

-- Funzione di utilità per il manuale, restituisce un elenco
-- delle proprietà supportate, divise per gruppo.
function p.properties(frame)
	local res = {}
	local cfg = readConfig()
	table.sort(orderedGroupNames)
	for _, groupName in ipairs(orderedGroupNames) do
		local wdLinks = {}
		for _, linkConf in ipairs(cfg[groupName]) do
			local wdLink = string.format('<tr><td>[[d:Property:%s|%s (%s)]]</td><td>%s</td><td>%s</td><td>%s</td></tr>',
				linkConf.pid, mWikidata._getLabel({ linkConf.pid }), linkConf.pid, linkConf.url or '', linkConf.cid or '', linkConf.template and ('[[t:'..linkConf.template..'|'..linkConf.template..']]') or '')
			table.insert(wdLinks, wdLink) 
		end
		local group = frame.args[1] == 'modulo' and
			string.format('* [[Modulo:Collegamenti esterni/%s]] (%s)', groupName, #wdLinks) or
			mw.getCurrentFrame():expandTemplate {
				title = 'Cassetto',
				args = {
					titolo = string.format('[[Modulo:Collegamenti esterni/%s|%s]] (%s)', groupName, groupName, #wdLinks),
					testo = '<table class="wikitable sortable plainlinks"><tr><th>Proprietà</th><th>Formato URL</th><th>cid</th><th>Template singolo</th></tr>' .. table.concat(wdLinks, '') .. '</table>'
				}
			}
		table.insert(res, group)
	end
	return table.concat(res, '\n')
end

-- Funzione di utilità per il manuale, verifica l'assenza di proprietà duplicate.
function p.checkdup(frame)
	local ids, res = {}, {}
	local cfg = readConfig()
	for _, groupName in ipairs(orderedGroupNames) do
		for _, linkConf in ipairs(cfg[groupName]) do
			if ids[linkConf.pid] then
				table.insert(res, linkConf.pid)
			else
				ids[linkConf.pid] = true
			end
		end
	end
	return #res == 0 and 'nessun duplicato' or
		string.format('<span class="error">%s</span>', table.concat(res, ', ')) 
end

-- Funzione per l'utilizzo da un altro modulo.
function p._main(args)
	return LinksManager:new(args):getList()
end

-- Funzione per il template {{Collegamenti esterni}}.
function p.main(frame)
	return p._main(getArgs(frame, { parentOnly = true }))
end

return p