Jump to content

Module:Wdinfobox

From Amateur Theatre Wiki
Revision as of 23:41, 1 March 2025 by PCAdmin (talk | contribs) (test)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Documentation for this module may be created at Module:Wdinfobox/doc

local p = {}

local PROP_INSTANCE_OF = 'P31'
local PROP_IMAGE = 'P18'
local PROP_MEDIA_LEGEND = 'P2096'
local PROP_DATE_OF_BIRTH = 'P569'
local PROP_PLACE_OF_BIRTH = 'P19'
local PROP_DATE_OF_DEATH = 'P570'
local PROP_PLACE_OF_DEATH = 'P20'
local PROP_FATHER = 'P22'
local PROP_MOTHER = 'P25'
local PROP_FORMATTER_URL = 'P1630'
local PROP_WIKISPORE_ID = 'P7721'
local PROP_ADMIN_LOCATION = 'P131'
local PROP_LOCATION = 'P276'
local PROP_COORDINATES = 'P625'

-- Configure Cargo tables and their columns.
local cargoConfig = {
	people = {
		wikidata = 'String',
		image = 'File',
		date_of_birth = 'Date',
		date_of_death = 'Date',
		parents = 'List (;) of String'
	},
	places = {
		wikidata = 'String',
		image = 'File',
		coords = 'Coordinates',
		admin_location = 'Page',
		location = 'Page'
	}
}

p.getTableRow = function( header, value, class )
	if ( header == nil or header == '' ) and ( value == nil or value == '' ) then
		return ''
	end
	-- Header.
	local th = mw.html.create( 'th' )
	th:wikitext( header )
	-- Data.
	local td = mw.html.create( 'td' )
	td:wikitext( mw.getCurrentFrame():preprocess( value ) )
	-- Row.
	local row = mw.html.create( 'tr' )
	row:attr( 'class', class )
	if header ~= nil and header ~= '' then
		row:node( th )
	else
		td:attr( 'colspan', '2' )
	end
	if value == nil or value == '' then
		th:attr( 'colspan', '2' )
	else
		row:node( td )
	end
	return row
end

p.getValue = function ( claim )
	for k,v in pairs( claim ) do
		-- Is of type item.
		if v.mainsnak.datatype == 'wikibase-item' then
			return '{{wdl|' .. v.mainsnak.datavalue.value.id .. '}}'
		elseif v.mainsnak.datatype == 'monolingualtext' then
			return v.mainsnak.datavalue.value.text
		elseif v.mainsnak.datatype == 'commonsMedia' then
			return '[[File:' .. v.mainsnak.datavalue.value .. '|300px]]'
		else
			return '<small><em>Type ' .. v.mainsnak.datatype .. ' not handled by Module:Infobox yet.</small>'
		end
	end
end

p.getErrorHtml = function ( msg )
	return '<span class="error">' .. msg .. '</span>'
end

p.dateAndPlace = function ( item, dataCargo, dateProp, placeProp )
	-- Dates.
	local datesData = item.claims[dateProp] or {}
	local dates = {}
	local lang = mw.language.getContentLanguage()
	for _,claim in pairs( datesData ) do
		dates[#dates + 1] = lang:formatDate('j F Y', claim.mainsnak.datavalue.value.time )
		if dateProp == PROP_DATE_OF_BIRTH then
			dataCargo.date_of_birth = claim.mainsnak.datavalue.value.time
		elseif dateProp == PROP_DATE_OF_DEATH then
			dataCargo.date_of_death = claim.mainsnak.datavalue.value.time
		end
	end
	local allDates = table.concat( dates, ' or ' )

	-- Places.
	local placesClaims = item.claims[placeProp] or {}
	local places = {}
	for _,claim in pairs( placesClaims ) do
		places[#places + 1] = '{{wdl|' .. claim.mainsnak.datavalue.value.id .. '}}'
	end
	local allPlaces = table.concat( places, ' or ' )

	-- Put them together.
	if allDates ~= '' and allPlaces ~= '' then
		return allDates .. ' in ' .. allPlaces
	else
		return allDates .. allPlaces
	end
end

p.parents = function( item, dataCargo )
	local out = {}
	local parentsIds = ''
	if item.claims[PROP_MOTHER] ~= nil then
		for _,claim in pairs( item.claims[PROP_MOTHER] ) do
			local motherId = claim.mainsnak.datavalue.value.id
			parentsIds = parentsIds .. ';' .. motherId
			out[#out +1] = '{{wdl|' .. motherId .. '}}'
		end
	end
	if item.claims[PROP_FATHER] ~= nil then
		for _,claim in pairs( item.claims[PROP_FATHER] ) do
			local fatherId = claim.mainsnak.datavalue.value.id
			parentsIds = parentsIds .. ';' .. fatherId
			out[#out +1] = '{{wdl|' .. fatherId .. '}}'
		end
	end
	if #out ~= 0 then
		dataCargo.parents = parentsIds
	end
	return out
end

p.children = function ( item )
    local results = mw.ext.cargo.query( 'people', 'wikidata', {
    	where = 'parents HOLDS "' .. item.id .. '"'
    } )
    local out = {}
    for _,res in pairs( results ) do
		out[#out +1] = '{{wdl|' .. res.wikidata .. '}}'
    end
    return out
end

p.getMap = function ( item, dataCargo )
	if item == nil or item.claims == nil or item.claims[PROP_COORDINATES] == nil then
		return
	end
	local point = item.claims[PROP_COORDINATES][0].mainsnak.datavalue.value.latitude .. ', '
			.. item.claims[PROP_COORDINATES][0].mainsnak.datavalue.value.longitude
	dataCargo.coords = point
	local mapParams = {
		'',
		point = point,
		zoom = '14',
		height = '300px',
		width = '100%'
	}
	return mw.getCurrentFrame():callParserFunction{ name = '#cargo_display_map', args = mapParams } 
end

p.getLabel = function ( id )
	local item = mw.ext.UnlinkedWikibase.getEntity( id )
	local contentLang = mw.language.getContentLanguage()
	local langCode = contentLang:getCode()
	-- Label in the content language.
	if item ~= nil and item.labels ~= nil and item.labels[langCode] ~= nil then
		return item.labels[langCode].value
	end
	-- Check fallback languages.
	local fallbackLangs = contentLang:getFallbackLanguages()
	for _,fallbackLang in pairs( fallbackLangs ) do
		if item.labels[fallbackLang] ~= nil then
			return item.labels[fallbackLang].value
		end
	end
	-- Otherwise, show an error.
	return 'No label for ' .. id
end

function getSitelinks( item, lang )
	if item.sitelinks == nil then
		return ''
	end
	local wikis = {
		-- Interwiki prefix, logo filename, Wikidata ID
		commonswiki  = { 'commons',    'Commons-logo',      'Q565' },
		enwiki       = { 'wikipedia',  'Wikipedia-logo-v2', 'Q52'  },
		enwikisource = { 'wikisource', 'Wikisource-logo',   'Q263' },
		enwikivoyage = { 'wikivoyage', 'Wikivoyage-logo',   'Q373' }
	}
	local sitelinks = {}
	for site,info in pairs( item.sitelinks ) do
		if wikis[site] == nil then
			-- Show bare site ID as link if it's not configured above.
			sitelinks[#sitelinks + 1] = '[' .. info.url .. ' ' .. site .. ']'
		else
			local prefix, logo, siteWikidataId = unpack( wikis[site] )
			local link = prefix .. ':' .. info.title
			local logo = '[[File:' .. logo .. '.svg|16x16px|alt=|link=' .. link .. ']]'
			sitelinks[#sitelinks + 1] = logo .. '&nbsp;[[' .. link .. '|' .. p.getLabel( siteWikidataId, lang ) .. ']]'
		end
	end
	return {
		class = 'mdl-infobox-sitelinks',
		values = sitelinks
	}
end

p.main = function( frame )
	local wikidataId = nil
	-- Check for 'wikidata' arg.
	if frame and frame.args and frame.args.wikidata ~= nil and frame.args.wikidata ~= '' then
		wikidataId = frame.args.wikidata
	end
	-- Check the calling template for 'wikidata' arg.
	if frame and type( frame.getParent ) == 'function' and frame:getParent().args and frame:getParent().args.wikidata then
		wikidataId = frame:getParent().args.wikidata
	end
	if wikidataId == nil then
		return p.getErrorHtml( 'Wikidata ID not provided.' )
	end
	local item = mw.ext.UnlinkedWikibase.getEntity( wikidataId )
	if item == nil or item.claims == nil then
		return p.getErrorHtml( 'Wikidata ID ' .. wikidataId .. ' not found.' )
	end
	if item == 'invalid-item-id' then
		return p.getErrorHtml( 'Wikidata ID ' .. wikidataId .. ' is invalid.' )
	end
	local lang = mw.getCurrentFrame():callParserFunction( 'int', 'lang' ) or "en"

	-- For each row of the infobox, collect a label, and a set of values (for cargo, and for display).
	local dataDisplay = {}
	local dataCargo = {}
	local rowNum = 1

	-- Images.
	if item.claims[PROP_IMAGE] ~= nil then
		dataDisplay[rowNum] = { class = 'mdl-infobox-image', label = '', values = {} }
		for claimNum,imageClaim in pairs( item.claims[PROP_IMAGE] ) do
			dataDisplay[rowNum].values[claimNum] = '[[File:' .. imageClaim.mainsnak.datavalue.value .. '|300x300px]]'
			-- Only add one image to Cargo data.
			dataCargo.image = imageClaim.mainsnak.datavalue.value
		end
		rowNum = rowNum + 1
	end

	-- Sitelinks
	local sitelinksList = getSitelinks( item, lang )
	if sitelinksList ~= nil then
		dataDisplay[rowNum] = sitelinksList
		rowNum = rowNum + 1
	end

	-- Administrative location
	local adminLocation = p.adminLocation( item, dataCargo )
	if adminLocation ~= nil then
		dataDisplay[rowNum] = adminLocation
		rowNum = rowNum + 1
	end

	-- Birth and death.
	local birth = p.dateAndPlace( item, dataCargo, PROP_DATE_OF_BIRTH, PROP_PLACE_OF_BIRTH )
	if birth ~= '' then
		dataDisplay[rowNum] = { class = 'mdl-infobox-birth', label = 'Born:', values = { birth } }
		rowNum = rowNum + 1
	end
	local death = p.dateAndPlace( item, dataCargo, PROP_DATE_OF_DEATH, PROP_PLACE_OF_DEATH )
	if death ~= '' then
		dataDisplay[rowNum] = { class = 'mdl-infobox-death', label = 'Died:', values = { death } }
		rowNum = rowNum + 1
	end
	
	-- Parents.
	local parents = p.parents( item, dataCargo )
	if #parents > 0 then
		dataDisplay[rowNum] = { class = 'mdl-infobox-parents', label = 'Parents:', values = parents }
		rowNum = rowNum + 1
	end
	
	-- Children.
	local children = p.children( item )
	if #children > 0 then
		dataDisplay[rowNum] = { class = 'mdl-infobox-children', label = 'Children:', values = children }
		rowNum = rowNum + 1
	end

	-- Map.
	local map = p.getMap( item, dataCargo )
	if map ~= nil then
		dataDisplay[rowNum] = { class = 'mdl-infobox-map', values = { map } }
		rowNum = rowNum + 1
	end

	-- Identifiers.
	dataDisplay[rowNum] = { class = 'mdl-infobox-authority-control-head', label = 'Authority control', values = {} }
	rowNum = rowNum + 1
	dataDisplay[rowNum] = { class = 'mdl-infobox-authority-control plainlinks', label = '', values = {} }
	dataDisplay[rowNum].values[1] = 'Wikidata: [[wikidata:' .. item.id .. '|' .. item.id .. ']]'
	dataCargo.wikidata = item.id
	local authControlIndex = 2
	for _,claim in pairs( item.claims ) do
		for snakNum,snak in pairs( claim ) do
			if snak.mainsnak.datatype == 'external-id' 
				and snak.mainsnak.snaktype == 'value'
				and snak.mainsnak.property ~= PROP_WIKISPORE_ID
			then
				local label = p.getLabel( snak.mainsnak.property )
				local value = snak.mainsnak.datavalue.value
				local authControlItem = mw.ext.UnlinkedWikibase.getEntity( snak.mainsnak.property )
				if authControlItem.claims[PROP_FORMATTER_URL] ~= nil then
					local url = string.gsub( authControlItem.claims[PROP_FORMATTER_URL][0].mainsnak.datavalue.value, '$1', value )
					value = '[' .. url .. ' ' .. value .. ']'
				end
				dataDisplay[rowNum].values[authControlIndex] = label .. ': ' .. value
				authControlIndex = authControlIndex + 1
			end
		end
	end
	rowNum = rowNum + 1

	-- Build the table.
	local ibTable = mw.html.create( 'table' )
	for _,row in pairs( dataDisplay ) do
		local value = ''
		-- Single value (seems that `#{row.values}` doesn't work?).
		if row.values[0] ~= nil and row.values[1] == nil then
			value = row.values[0]
		elseif row.values[1] ~= nil then
			-- Multiple values (shown in a list).
			value = '<ul>'
			for _,val in pairs( row.values ) do
				value = value .. '<li>' .. val .. '</li>'
			end
			value = value .. '</ul>'
		end
		ibTable:node( p.getTableRow( row.label, value, row.class ) )
	end
	local setPageWikibaseId = mw.getCurrentFrame():callParserFunction{ name = '#unlinkedwikibase', args = { '', id = item.id } }
	local styles = mw.getCurrentFrame():extensionTag( 'templatestyles', nil, { src = 'Module:Infobox/styles.css' } )
	local ib = mw.html.create( 'div' )
	ib:attr( 'class', 'mdl-infobox' )
	if item.labels.en ~= nil then
		ib:wikitext( "<div class='mdl-infobox-label'>'''" .. item.labels.en.value .. "'''" )
		if item.descriptions.en ~= nil then
			ib:wikitext( "<br>" .. item.descriptions.en.value .. "" )
		end
		ib:wikitext( '</div>' )
	end
	ib:node( ibTable )
	local cargoStore = p.getCargoStore( frame.args, dataCargo )
	return styles .. setPageWikibaseId .. cargoStore .. tostring( ib )
end

p.adminLocation = function( item, dataCargo )
	if item.claims[PROP_ADMIN_LOCATION] == nil then
		return nil
	end
	local values = {}
	local label = 'Administrative location'
	for claimNum,claim in pairs( item.claims[PROP_ADMIN_LOCATION] ) do
		local locationItem = mw.ext.UnlinkedWikibase.getEntity( claim.mainsnak.datavalue.value.id )
		local localTitle = mw.ext.UnlinkedWikibase.getLocalTitle( claim.mainsnak.datavalue.value.id )
		dataCargo.admin_location = claim.mainsnak.datavalue.value.id
		if localTitle ~= nil then
			dataCargo.admin_location = localTitle.prefixedText
		end
		if locationItem.claims ~= nil and locationItem.claims[PROP_INSTANCE_OF] ~= nil then
			-- Get first 'instance of' label.
			local locationInstanceItem = mw.ext.UnlinkedWikibase.getEntity( locationItem.claims[PROP_INSTANCE_OF][0].mainsnak.datavalue.value.id )
			if locationInstanceItem.labels ~= nil and locationInstanceItem.labels.en ~= nil then
				label = mw.getContentLanguage():ucfirst( locationInstanceItem.labels.en.value )
			end
		end
		values[#values +1] = '{{wdl|' .. claim.mainsnak.datavalue.value.id .. '}}'
	end
	return {
		class = 'mdl-infobox-admin-location',
		label = label .. ':',
		values = values
	}
end

-- --
-- Get the {{#cargo_declare}} parser function, for use on a template page.
--
p.cargoDeclare = function ( frame )
	if frame.args.cargo_table ~= nil and frame.args.cargo_table ~= '' and cargoConfig[frame.args.cargo_table] ~= nil then
		local declareParts = {}
		for col,type in pairs( cargoConfig[frame.args.cargo_table] ) do
			declareParts[#declareParts + 1] = col .. ' = ' .. type
		end
		local declareFull = '{{#cargo_declare: _table= ' .. frame.args.cargo_table .. '\n | '.. table.concat( declareParts, "\n | " ) .. '\n}}'
		if mw.getCurrentFrame():getParent() ~= nil then
			-- There's no parent frame when testing.
	    	return mw.getCurrentFrame():getParent():preprocess( '<noinclude>' .. declareFull .. '</noinclude>' )
    	end
	end
	return ''
end

-- --
-- Get {{#cargo_store}} parser function, wrapped in an includeonly tag.
-- @param {table} args Main module arguments.
-- @param {table} dataCargo Data values, keyed by column name.
-- @return string
--
p.getCargoStore = function ( args, dataCargo )
	if args.cargo_table == nil or args.cargo_table == '' or cargoConfig[args.cargo_table] == nil then
		return ''
	end
	local storeParts = {}
	for col,type in pairs( cargoConfig[args.cargo_table] ) do
		if dataCargo[ col ] ~= nil then
			storeParts[#storeParts + 1] = col .. ' = ' .. dataCargo[ col ]
		end
	end
	local storeFull = '{{#cargo_store: _table= ' .. args.cargo_table .. '\n | '.. table.concat( storeParts, "\n | " ) .. '\n}}'
	if mw.getCurrentFrame():getParent() ~= nil then
		-- There's no parent frame when testing.
		return mw.getCurrentFrame():getParent():preprocess( '<includeonly>' .. storeFull .. '</includeonly>' )
	end
	return ''
end

-- =p.main({args={wikidata='Q8018316'}})
-- =p.main({args={wikidata='Q5501472', cargo_table='places'}})
return p