Module:InfoboxWB
From Bahaipedia
						
						This is a helper module for retrieving data from bahaidata.org. It is intended to be referenced by a template which is used on a page that is already connected to a wikibase item. It does not support retrieving specific items or their properties from bahaidata since this module is specific to infoboxes used on pages which should always have a corresponding connected bahaidata page.
Functions[edit]
- getImage
 - getImageCaption
 - getBirthName
 - getBirthDate
 - getBirthPlace
 - getBirthCountry
 - getDeclarationDate
 - getDeathDate
 - getDeathPlace
 - getDeathCountry
 - getDeceasedAge (The age of the individual at the time of their death)
 - getSisterProjects
 - getPositionsWithDates
 
Parameters[edit]
- lang, used to specify a language you want retrieved and displayed if other then English. If the requested language value doesn't exist then the English language value is displayed.
 - format, used to modify the display format for dates, options are mdy (default) and dmy
 
Examples[edit]
Basic[edit]
- {{#invoke:InfoboxWB|getDeathPlace}}
 - {{#invoke:InfoboxWB|getImageCaption}}
 - {{#invoke:InfoboxWB|getDeathDate}}
 
Lang parameter[edit]
- {{#invoke:InfoboxWB|getImageCaption|lang=es}}
 
Format parameter[edit]
- {{#invoke:InfoboxWB|getDeathDate|format=dmy}}
 
To do[edit]
The adaptation so far is intended for use with Template:Infobox Person but more infoboxes can be supported by adding more functions. Need to identify other data which is suitable, what functions are needed and add/test/integrate them back into the respective templates.
-- Module:InfoboxWB
local p = {}
local mw = require('mw')
-- Helper function to determine if a page is connected to bahaidata
function p.isEntityConnected()
    local entity = mw.wikibase.getEntity()
    return entity ~= nil
end
-- Helper function to fetch property value from entity
local function fetchPropertyValue(entity, propertyId)
    if not entity or not propertyId then
        return nil
    end
    local property = entity:getBestStatements(propertyId)[1]
    if property and property.mainsnak and property.mainsnak.datavalue then
        return property.mainsnak.datavalue.value.id or property.mainsnak.datavalue.value
    end
    return nil
end
-- Helper function to fetch sitelink from entity
--local function fetchSiteLinkValue(entity, site)
--    if not entity or not site then
--        return nil
--    end
--    local siteLink = entity:getSiteLink(site)[1]
--    if siteLink and siteLink.mainsnak and siteLink.mainsnak.datavalue then
--        -- Assuming the property value is a simple string or a numeric ID
--        return siteLink.mainsnak.datavalue.value.id or siteLink.mainsnak.datavalue.value
--    end
--    return nil
--end
-- Function to get the label of an item in a specified language
local function getItemLabel(itemId, lang)
    if not itemId then
        return nil
    end
    local item = mw.wikibase.getEntity(itemId)
    if item then
        local label = item:getLabel(lang) -- Attempt to get the label in the specified language
        if label then
            return label
        else
            return item:getLabel('en') -- Fallback to English label if specified language label is not available
        end
    end
    return nil
end
-- Function to fetch monolingual text properties with a fallback to English
function p.getMonolingualText(entity, propertyId, lang)
    local statements = entity:getBestStatements(propertyId)
    local fallbackText = nil  -- Variable to store the English text
    for _, statement in ipairs(statements) do
        if statement.mainsnak and statement.mainsnak.datavalue then
            local textData = statement.mainsnak.datavalue.value
            if textData and textData.text then
                if textData.language == lang then
                    return textData.text  -- Return the text for the specified language
                elseif textData.language == 'en' then
                    fallbackText = textData.text  -- Store the English text as a fallback
                end
            end
        end
    end
    return fallbackText  -- Return the English text if no text in the specified language is found
end
-- Function to fetch monolingual qualifiers of a property with a fallback to English
function p.getMonolingualTextFromQualifier(qualifiers, propertyId, lang)
    local fallbackText = nil  -- Variable to store the English text
    if qualifiers and qualifiers[propertyId] then
        for _, qualifier in ipairs(qualifiers[propertyId]) do
            if qualifier.datavalue then
                local textData = qualifier.datavalue.value
                if textData and textData.text then
                    if textData.language == lang then
                        return textData.text  -- Return the text for the specified language
                    elseif textData.language == 'en' then
                        fallbackText = textData.text  -- Store the English text as a fallback
                    end
                end
            end
        end
    end
    return fallbackText  -- Return the English text if no text in the specified language is found
end
-- Function to format dates
local function formatDate(timeValue, format)
    if not timeValue or type(timeValue) ~= 'table' or not timeValue.time then
        return nil
    end
    -- Extracting the time string from the timeValue table
    local timeString = timeValue.time
    if not timeString then
        return nil
    end
    -- The time string might start with a '+' sign, which needs to be removed for formatting
    timeString = timeString:gsub("^%+", "")
    -- Formatting the date
    if format == 'dmy' then
        return mw.language.getContentLanguage():formatDate('j F Y', timeString)
    else -- default to 'mdy'
        return mw.language.getContentLanguage():formatDate('F j, Y', timeString)
    end
end
function p.getImage(frame)
    local entityId = mw.wikibase.getEntityIdForCurrentPage()
    if not entityId then
        return nil  -- No entity ID, return nil
    end
    local entity = mw.wikibase.getEntity(entityId)
    if not entity then
        return nil  -- No entity data, return nil
    end
    local imageProperty = entity:getBestStatements('P35')[1]
    if imageProperty and imageProperty.mainsnak and imageProperty.mainsnak.datavalue then
        local image = imageProperty.mainsnak.datavalue.value
        if image then
            return image  -- Return the image filename
        end
    end
    return nil  -- No image found, return nil
end
function p.getImageCaption(frame)
    local entityId = mw.wikibase.getEntityIdForCurrentPage()
    if not entityId then
        return nil  -- No entity ID, return nil
    end
    local entity = mw.wikibase.getEntity(entityId)
    if not entity then
        return nil  -- No entity data, return nil
    end
    local statements = entity:getBestStatements('P35')
    if #statements == 0 then
        return nil  -- No image property, return nil
    end
    local imageProperty = statements[1]
    local lang = frame.args.lang or 'en'
    return p.getMonolingualTextFromQualifier(imageProperty.qualifiers, 'P40', lang)
end
function p.getBirthName(frame)
    local entityId = mw.wikibase.getEntityIdForCurrentPage()
    if not entityId then return nil end
    local entity = mw.wikibase.getEntity(entityId)
    local lang = frame.args.lang or 'en'
    return p.getMonolingualText(entity, 'P41', lang)
end
function p.getBirthDate(frame)
    local entityId = mw.wikibase.getEntityIdForCurrentPage()
    if not entityId then return nil end
    local entity = mw.wikibase.getEntity(entityId)
    local birthDate = fetchPropertyValue(entity, 'P16')
    -- Check if birthDate contains only a year (e.g., '+1960-00-00T00:00:00Z')
    if birthDate and type(birthDate) == 'table' and birthDate.time and string.match(birthDate.time, "^%+%d%d%d%d%-00%-00") then
        return string.sub(birthDate.time, 2, 5) 
    end
    return birthDate and formatDate(birthDate, frame.args.format)
end
function p.getDeclarationDate(frame)
    local entityId = mw.wikibase.getEntityIdForCurrentPage()
    if not entityId then return nil end
    local entity = mw.wikibase.getEntity(entityId)
    local declarationDate = fetchPropertyValue(entity, 'P45')
    -- Check if declarationDate contains only a year (e.g., '+1960-00-00T00:00:00Z')
    if declarationDate and type(declarationDate) == 'table' and declarationDate.time and string.match(declarationDate.time, "^%+%d%d%d%d%-00%-00") then
        return string.sub(declarationDate.time, 2, 5) 
    end
    return declarationDate and formatDate(declarationDate, frame.args.format)
end
function p.getBirthPlace(frame)
    local entityId = mw.wikibase.getEntityIdForCurrentPage()
    if not entityId then return nil end
    local entity = mw.wikibase.getEntity(entityId)
    local birthPlaceId = fetchPropertyValue(entity, 'P19')
    local lang = frame.args.lang or 'en'
    return getItemLabel(birthPlaceId, lang)
end
function p.getBirthCountry(frame)
    local entityId = mw.wikibase.getEntityIdForCurrentPage()
    if not entityId then return nil end
    local entity = mw.wikibase.getEntity(entityId)
    local birthPlaceId = fetchPropertyValue(entity, 'P19')
    if not birthPlaceId then return nil end
    local birthPlaceEntity = mw.wikibase.getEntity(birthPlaceId)
    local countryId = fetchPropertyValue(birthPlaceEntity, 'P37')
    local lang = frame.args.lang or 'en'
    return getItemLabel(countryId, lang)
end
function p.getPositionsWithDates(frame)
    local entity = mw.wikibase.getEntity()
    local statements = entity:getBestStatements('P55') -- Position held
    if not statements or #statements == 0 then
        return ""
    end
    local lang = frame.args.lang or 'en'
    local positionData = {}
    -- Define a mapping for institution prefixes to their full names and link text
    local institutionMapping = {
        ["NSA"] = {link = "National Spiritual Assembly", text = "NSA member"},
        ["Universal House of Justice"] = {link = "Universal House of Justice", text = "UHJ member"},
        ["International Teaching Centre"] = {link = "International Teaching Centre", text = "ITC member"},
        ["LSA"] = {link = "Local Spiritual Assembly", text = "LSA member"},
        ["Auxiliary Board for"] = {link = "Auxiliary Board", text = "ABM"},
        ["Continental Board of Counsellors for"] = {link = "Continental Boards of Counsellors", text = "Counsellor"},
        ["International Bahá’í Council"] = {link = "International Bahá'í Council", text = "IBC member"},
        ["Custodian"] = {link = "Custodian", text = "Custodian"}
    }
    for _, statement in ipairs(statements) do
        local mainsnak = statement.mainsnak
        if mainsnak and mainsnak.datavalue then
            local positionId = mainsnak.datavalue.value.id
            local label = getItemLabel(positionId, lang) or positionId
            -- Determine institution and display name
            local prefix, displayName
            local institution
            if label:match("^(NSA):(.+)$") or label:match("^(LSA):(.+)$") then
                -- Extract the prefix and location for NSA and LSA
                prefix, displayName = label:match("^(%a+):(.+)$")
                institution = institutionMapping[prefix]
            elseif label:find("^Auxiliary Board for") or label:find("^Continental Board of Counsellors for") then
                -- Extract the region for Auxiliary Board and Continental Board
                local pattern = "^(Auxiliary Board for) (.+)$"
                if label:find("^Continental Board of Counsellors for") then
                    pattern = "^(Continental Board of Counsellors for) (.+)$"
                end
                prefix, displayName = label:match(pattern)
                institution = institutionMapping[prefix]
            else
                -- Handle full names directly for other specific institutions
                displayName = label
                institution = institutionMapping[label]
            end
            -- Get qualifiers
            local qualifiers = statement.qualifiers or {}
            local startTime = qualifiers['P56'] and qualifiers['P56'][1] and qualifiers['P56'][1].datavalue and qualifiers['P56'][1].datavalue.value.time
            local endTime = qualifiers['P57'] and qualifiers['P57'][1] and qualifiers['P57'][1].datavalue and qualifiers['P57'][1].datavalue.value.time
            local startYear = startTime and mw.ustring.match(startTime, "%d%d%d%d")
            local endYear = endTime and mw.ustring.match(endTime, "%d%d%d%d")
            table.insert(positionData, {
                label = label,
                displayName = displayName,
                institution = institution,
                startYear = startYear,
                endYear = endYear,
                prefix = prefix, -- Store prefix for linking later
                sortKey = startTime or "9999" -- Put undated entries at the end
            })
        end
    end
    -- Sort by sortKey (chronological order)
    table.sort(positionData, function(a, b)
        return a.sortKey < b.sortKey
    end)
    
    -- Group results by label
    if #positionData == 0 then return "" end
    local groupedData = {}
    local currentGroup = nil
    for _, entry in ipairs(positionData) do
        if not currentGroup or currentGroup.label ~= entry.label then
            -- If a new group starts, add the previous one to the list
            if currentGroup then
                table.insert(groupedData, currentGroup)
            end
            -- Start a new group
            currentGroup = {
                label = entry.label,
                displayName = entry.displayName,
                institution = entry.institution,
                prefix = entry.prefix,
                dates = {}
            }
        end
        
        -- Add the date string to the current group
        local dateStr = ""
        if entry.startYear and entry.endYear then
            dateStr = entry.startYear .. " - " .. entry.endYear
        elseif entry.startYear then
            dateStr = entry.startYear .. " - Present"
        end
        if dateStr ~= "" then
            table.insert(currentGroup.dates, dateStr)
        end
    end
    -- Add the very last group
    if currentGroup then
        table.insert(groupedData, currentGroup)
    end
    -- Format grouped results
    local results = {}
    for _, group in ipairs(groupedData) do
        if group.institution then
            local dateStr = table.concat(group.dates, "<br>")
            -- Format as a table row
            local row = mw.html.create('tr')
            row:tag('th')
                :attr('scope', 'row')
                :addClass('infobox-label')
                :wikitext("[[" .. group.institution.link .. "|" .. group.institution.text .. "]]")
            local displayLink
            if group.prefix == "Auxiliary Board for" then
                displayLink = "[[Auxiliary Board#" .. group.displayName .. "|" .. group.displayName .. "]]"
            elseif group.prefix == "Continental Board for" then
                displayLink = "[[Continental Board for " .. group.displayName .. "|" .. group.displayName .. "]]"
            elseif group.institution.text == "UHJ member" or group.institution.text == "ITC member" or group.institution.text == "IBC member" then
                displayLink = ""
            else
                displayLink = "[[" .. group.label .. "|" .. group.displayName .. "]]"
            end
            if group.institution.text == "UHJ member" or group.institution.text == "ITC member" or group.institution.text == "IBC member" then
                row:tag('td')
                    :addClass('infobox-data')
                    :wikitext(dateStr)
            else
                row:tag('td')
                    :addClass('infobox-data')
                    :wikitext(displayLink .. "<br>" .. dateStr)
            end
            table.insert(results, tostring(row))
            -- Add category if applicable
            if group.institution.text == "NSA member" then
                table.insert(results, "[[Category:Biographies of National Spiritual Assembly members]]")
            elseif group.institution.text == "UHJ member" then
                table.insert(results, "[[Category:Biographies of Universal House of Justice members]]")
            elseif group.institution.text == "ITC member" then
                table.insert(results, "[[Category:Biographies of International Teaching Centre members]]")
            elseif group.institution.text == "ABM" then
                table.insert(results, "[[Category:Biographies of Auxiliary Board members]]")
            elseif group.institution.text == "Counsellor" then
                table.insert(results, "[[Category:Biographies of Counsellors]]")
            elseif group.institution.text == "IBC member" then
                table.insert(results, "[[Category:Biographies of International Bahá'í Council members]]")
            elseif group.institution.text == "Custodian" then
                table.insert(results, "[[Category:Biographies of Custodians]]")
            end
        end
    end
    return table.concat(results, "")
end
function p.getDeathDate(frame)
    local entityId = mw.wikibase.getEntityIdForCurrentPage()
    if not entityId then return nil end
    local entity = mw.wikibase.getEntity(entityId)
    local deathDate = fetchPropertyValue(entity, 'P17')
    -- Check if deathDate contains only a year (e.g., '+1980-00-00T00:00:00Z')
    if deathDate and type(deathDate) == 'table' and deathDate.time and string.match(deathDate.time, "^%+%d%d%d%d%-00%-00") then
        return string.sub(deathDate.time, 2, 5) 
    end
    return deathDate and formatDate(deathDate, frame.args.format)
end
function p.getDeathPlace(frame)
    local entityId = mw.wikibase.getEntityIdForCurrentPage()
    if not entityId then return nil end
    local entity = mw.wikibase.getEntity(entityId)
    local deathPlaceId = fetchPropertyValue(entity, 'P18')
    local lang = frame.args.lang or 'en'
    return getItemLabel(deathPlaceId, lang)
end
function p.getDeathCountry(frame)
    local entityId = mw.wikibase.getEntityIdForCurrentPage()
    if not entityId then return nil end
    local entity = mw.wikibase.getEntity(entityId)
    local deathPlaceId = fetchPropertyValue(entity, 'P18')
    if not deathPlaceId then return nil end
    local deathPlaceEntity = mw.wikibase.getEntity(deathPlaceId)
    local countryId = fetchPropertyValue(deathPlaceEntity, 'P37')
    local lang = frame.args.lang or 'en' 
    return getItemLabel(countryId, lang)
end
function p.getDeceasedAge(frame)
    local entityId = mw.wikibase.getEntityIdForCurrentPage()
    if not entityId then
        return nil
    end
    local entity = mw.wikibase.getEntity(entityId)
    local birthDate = fetchPropertyValue(entity, 'P16')
    local deathDate = fetchPropertyValue(entity, 'P17')
    -- Check if both birthDate and deathDate are available and contain full dates (year, month, day)
    if not birthDate or not birthDate.time or birthDate.time:match("00%-00") then
        return nil
    end
    if not deathDate or not deathDate.time or deathDate.time:match("00%-00") then
        return nil
    end
    -- Parse dates
    local birthYear, birthMonth, birthDay = birthDate.time:match("(%d%d%d%d)%-(%d%d)%-(%d%d)")
    local deathYear, deathMonth, deathDay = deathDate.time:match("(%d%d%d%d)%-(%d%d)%-(%d%d)")
    birthYear, birthMonth, birthDay = tonumber(birthYear), tonumber(birthMonth), tonumber(birthDay)
    deathYear, deathMonth, deathDay = tonumber(deathYear), tonumber(deathMonth), tonumber(deathDay)
    -- Calculate age
    local age = deathYear - birthYear
    if birthMonth > deathMonth or (birthMonth == deathMonth and birthDay > deathDay) then
        age = age - 1
    end
    return age
end
function p.getSisterProjects(frame)
	local s = {}
    local entityId = mw.wikibase.getEntityIdForCurrentPage()
    if not entityId then return nil end
    local entity = mw.wikibase.getEntity(entityId)
    
    if entity and entity.sitelinks then 					-- See if entity exists, and that it has sitelinks
		for i, j in pairs(entity.sitelinks) do 				-- loop over all sitelinks
			if mw.ustring.sub( j.site, mw.ustring.len(j.site) - 4 ) == 'works' then	-- See which are to works
				table.insert(s, '[[File:BW-favicon.png|16px| ]] [[:works:' .. j.title .. '|Works]]')	-- Create a table of sites and sitelinks
			elseif mw.ustring.sub( j.site, mw.ustring.len(j.site) - 4 ) == 'media' then	-- See which are to media
				table.insert(s, '[[File:Fav.png| ]] [[:c:' .. j.title .. '|Media]]')	-- Create a table of sites and sitelinks
			end
		end
	end
	return table.concat(s, ' • ')							-- Return as string
end
return p