Module:Wiki Loves Living Heritage
Appearance
-- This module includes little helper functions for the Wiki Loves Living Heritage pages
local p = {}
-- Any time you use {{#invoke:}}, the object passed in is a frame (representing the page using the module as well as arguments passed to the {{#invoke:}} call).
function p.wikidata_source_link(frame)
local entity_id = frame.args[1] -- Wikidata entity ID
local property_id = "P856" -- Wikidata property ID
local value = mw.wikibase.getEntity(entity_id):getPropertyValue(property_id)
local source_url = mw.wikibase.getBestStatements(entity_id, property_id)[1]:getSourceUrl()
return string.format("[[%s|%s]]", source_url, value)
end
function p.wikipediaLink(frame)
local entity = frame.args[1] -- Wikidata entity ID
local lang = frame.args[2] -- Wikidata property ID
local link
if type(entity) == 'table' then
link = entity:getSitelink(lang .. 'wiki')
else
link = mw.wikibase.getSitelink(entity, lang .. 'wiki')
end
if link then
linktxt = ':' .. lang .. ':' .. link
return string.format("[[%s|%s]]", linktxt, link)
end
return nil
end
function p.organizerlistsimple(frame)
local output = ""
-- Get the first argument passed to the {{#invoke:}} call (after the name of the module and the function name).
local input = frame.args[1]
-- Use 'mw.ustring' rather than 'string', since strings that are passed in are almost always Unicode text.
for i in mw.ustring.gmatch(input, "[^,]+") do
-- Get the result from using the {{label}} template, with i as an argument, on this page.
current_label = frame:expandTemplate{
title='Wiki Loves Living Heritage/Organizerlinksimple',
args={i}
}
-- Add a bullet point before every label except the first.
if output ~= "" then
output = output .. ", "
end
-- Add the current label to the output.
output = output .. current_label
end
return output
end
function p.organizerlist(frame)
local output = ""
-- Get the first argument passed to the {{#invoke:}} call (after the name of the module and the function name).
local input = frame.args[1]
-- Use 'mw.ustring' rather than 'string', since strings that are passed in are almost always Unicode text.
for i in mw.ustring.gmatch(input, "[^,]+") do
-- Get the result from using the {{label}} template, with i as an argument, on this page.
current_label = frame:expandTemplate{
title='Wiki Loves Living Heritage/Organizerlink',
args={i}
}
-- Add a bullet point before every label except the first.
if output ~= "" then
output = output .. "<br />"
end
-- Add the current label to the output.
output = output .. current_label
end
return output
end
local function getPreferredFlag(entity)
if not entity or not entity.claims or not entity.claims.P41 then
return nil
end
local normal = nil
for _, claim in ipairs(entity.claims.P41) do
if claim.rank == "preferred"
and claim.mainsnak
and claim.mainsnak.datavalue then
return claim.mainsnak.datavalue.value
end
-- remember first normal-rank claim as fallback
if not normal
and claim.rank == "normal"
and claim.mainsnak
and claim.mainsnak.datavalue then
normal = claim.mainsnak.datavalue.value
end
end
return normal
end
function p.labellist(frame)
local input = frame.args[1]
local showFlags = frame.args.flags == "yes"
if not input or input == "" then
return ""
end
local output = ""
for id in mw.ustring.gmatch(input, "[^,]+") do
id = mw.text.trim(id)
local current_label = frame:expandTemplate{
title = 'Wiki Loves Living Heritage/Originlink',
args = { id }
}
local flag = nil
if showFlags then
local entity = mw.wikibase.getEntity(id)
if entity then
flag = getPreferredFlag(entity)
end
end
if output ~= "" then
output = output .. " • "
end
if flag then
output = output .. string.format(
'[[File:%s|20px|border]] %s',
flag,
current_label
)
else
output = output .. current_label
end
end
return '<div class="labellist">' .. output .. '</div>'
end
function p.ichlinksFromItem(frame)
local args = frame.args
local qid = args.item
local userLang = args.lang or "en"
-- UNESCO ICH lists handled by this function
local allowedLists = {
Q110319947 = true, -- Representative List
Q17323370 = true, -- Urgent Safeguarding List
Q17325849 = true -- Register of Good Safeguarding Practices
}
if not qid then
return ""
end
local entity = mw.wikibase.getEntity(qid)
if not entity or not entity.claims or not entity.claims.P3259 then
return ""
end
local output = {}
for _, claim in ipairs(entity.claims.P3259) do
-- Skip deprecated statements
if claim.rank ~= "deprecated" then
local valueQid = claim.mainsnak.datavalue.value.id
-- Only handle allowed UNESCO ICH lists
if allowedLists[valueQid] then
local processStatement = false
local statementIchid = nil
local urlCandidate = nil
-- 1️⃣ P10221 on statement
if claim.qualifiers
and claim.qualifiers.P10221
and claim.qualifiers.P10221[1]
and claim.qualifiers.P10221[1].datavalue then
statementIchid = claim.qualifiers.P10221[1].datavalue.value
processStatement = true
end
-- 2️⃣ P10221 in references
if not statementIchid and claim.references then
for _, ref in pairs(claim.references) do
local snaks = ref.snaks or {}
if snaks.P10221 and snaks.P10221[1] and snaks.P10221[1].datavalue then
statementIchid = snaks.P10221[1].datavalue.value
processStatement = true
break
end
end
end
-- 3️⃣ P854-only fallback (UNESCO URL)
if not statementIchid and claim.references then
for _, ref in pairs(claim.references) do
local snaks = ref.snaks or {}
if snaks.P854 and snaks.P854[1] and snaks.P854[1].datavalue then
local refUrl = snaks.P854[1].datavalue.value
if refUrl:match("^https://ich%.unesco%.org/") then
urlCandidate = refUrl
processStatement = true
break
end
end
end
end
if processStatement then
-- Determine prefix/number for ICH URL if P10221 exists
local prefix, num
if statementIchid then
prefix = mw.ustring.match(statementIchid, "^(%u+)")
num = mw.ustring.match(statementIchid, "/(%d+)$")
end
-- 4️⃣ Title lookup
local bestMatch
local fallbackMatch
if claim.references then
for _, ref in pairs(claim.references) do
local snaks = ref.snaks or {}
local url, title, titleLang
-- Use P854 URL if present
if snaks.P854 and snaks.P854[1] and snaks.P854[1].datavalue then
local refUrl = snaks.P854[1].datavalue.value
if refUrl:match("^https://ich%.unesco%.org/") then
url = refUrl
end
end
-- Reference P1476
if snaks.P1476 and snaks.P1476[1] and snaks.P1476[1].datavalue then
local val = snaks.P1476[1].datavalue.value
title = val.text
titleLang = val.language
end
if url and title then
if titleLang == userLang then
bestMatch = { url = url, title = title }
break
elseif titleLang == "en" then
fallbackMatch = { url = url, title = title }
end
end
end
end
-- Statement qualifier P1476
if not bestMatch and claim.qualifiers and claim.qualifiers.P1476 then
for _, qual in ipairs(claim.qualifiers.P1476) do
if qual.datavalue then
local val = qual.datavalue.value
if val.language == userLang then
bestMatch = { url = nil, title = val.text }
break
elseif val.language == "en" then
fallbackMatch = { url = nil, title = val.text }
end
end
end
end
local chosen = bestMatch or fallbackMatch
-- Fallback to inventory label
if not chosen then
local inventoryLabel =
mw.wikibase.getLabelByLang(valueQid, userLang)
or mw.wikibase.getLabelByLang(valueQid, "en")
or ""
chosen = { url = nil, title = inventoryLabel }
end
-- Determine final URL
if not chosen.url then
if statementIchid then
chosen.url = string.format(
"https://ich.unesco.org/%s/%s/%s",
userLang, prefix, num
)
elseif urlCandidate then
chosen.url = urlCandidate
end
end
if chosen.url and chosen.title then
table.insert(
output,
string.format(
'[[File:UNESCO-ICH-blue.svg|20px]] [%s %s]',
chosen.url,
chosen.title
)
)
end
end
end
end
end
return table.concat(output, "<br />")
end
local function getPreferredReferenceUrl(claim, lang)
if not claim.references then
claim.references = {} -- ensure we can iterate
end
-- Map lang code to Wikidata language item, if possible
local langQid = nil
if lang and mw.wikibase.getEntityIdForLanguageCode then
langQid = mw.wikibase.getEntityIdForLanguageCode(lang)
end
local fallbackNoLang = nil
local fallbackAny = nil
-- 1️⃣ Try URLs from references with language match
for _, ref in ipairs(claim.references) do
if ref.snaks and ref.snaks.P854 and ref.snaks.P854[1].datavalue then
local url = ref.snaks.P854[1].datavalue.value
fallbackAny = fallbackAny or url
if ref.snaks.P407 and ref.snaks.P407[1].datavalue then
local refLangQid = ref.snaks.P407[1].datavalue.value.id
if langQid and refLangQid == langQid then
return url
end
else
fallbackNoLang = fallbackNoLang or url
end
end
end
-- 2️⃣ Return fallback URL from references if available
if fallbackNoLang or fallbackAny then
return fallbackNoLang or fallbackAny
end
-- 3️⃣ Last-resort: fetch P856 of the P3259 entity
if claim.mainsnak and claim.mainsnak.datavalue and claim.mainsnak.datavalue.value then
local p3259Qid = claim.mainsnak.datavalue.value.id
if p3259Qid then
local p3259Entity = mw.wikibase.getEntity(p3259Qid)
if p3259Entity and p3259Entity.claims and p3259Entity.claims.P856 then
local p856Claim = p3259Entity.claims.P856[1]
if p856Claim and p856Claim.mainsnak and p856Claim.mainsnak.datavalue then
return p856Claim.mainsnak.datavalue.value
end
end
end
end
return nil
end
local function getInventoryFlags(invId)
local invEntity = mw.wikibase.getEntity(invId)
if not invEntity or not invEntity.claims or not invEntity.claims.P17 then
return ''
end
local flags = {}
local seenFlags = {} -- track which flag files we've already added
for _, countryClaim in ipairs(invEntity.claims.P17) do
if countryClaim.mainsnak
and countryClaim.mainsnak.datavalue
and countryClaim.mainsnak.datavalue.value
then
local countryId = countryClaim.mainsnak.datavalue.value.id
local countryEntity = mw.wikibase.getEntity(countryId)
if countryEntity
and countryEntity.claims
and countryEntity.claims.P41
and countryEntity.claims.P41[1].mainsnak.datavalue
then
local flagFile =
countryEntity.claims.P41[1].mainsnak.datavalue.value
if not seenFlags[flagFile] then
table.insert(
flags,
"<span class='flag'>[[File:" ..
flagFile ..
"|frameless|20x20px|link=]]</span>"
)
seenFlags[flagFile] = true
end
end
end
end
if #flags > 0 then
return table.concat(flags, ' ') .. ' '
end
return ''
end
function p.inventorylist(frame)
local item = frame.args.item
if not item then return '' end
local lang = frame.args.lang
local entity = mw.wikibase.getEntity(item)
if not entity or not entity.claims or not entity.claims.P3259 then return '' end
local unescoQids = {
["Q110319947"] = true,
["Q17323370"] = true,
["Q17325849"] = true
}
local deprecatedUnescoQid = "Q877988"
local output = {}
for _, claim in ipairs(entity.claims.P3259) do
local invId = claim.mainsnak.datavalue.value.id
local isExcludedUnesco = unescoQids[invId]
local isDeprecatedUnesco = (invId == deprecatedUnescoQid)
if not isExcludedUnesco or isDeprecatedUnesco then
local label = mw.wikibase.getLabel(invId) or invId
-- language-aware URL
local url = getPreferredReferenceUrl(claim, lang)
-- prefix: either ICH logo or multiple flags
local prefix = ''
if isDeprecatedUnesco then
prefix = "<span class='ich-logo'>[[File:UNESCO-ICH-blue.svg|20px|link=]]</span> "
else
prefix = getInventoryFlags(invId)
end
local rendered
if url then
rendered = prefix .. string.format('[%s %s]', url, label)
else
rendered = prefix .. label
end
table.insert(output, "<div class='boxrow'>" .. rendered .. "</div>")
end
end
return table.concat(output)
end
function p.participantlist(frame)
local output = ""
local arguments = {}
local input = frame.args[1]
for i in mw.ustring.gmatch(input, "[^,]+") do
arguments['item'] = i
current_label = frame:expandTemplate{
title='Wiki Loves Living Heritage/Contact',
args = arguments
}
output = output .. current_label
end
return output
end
-- Function to convert Solar Hijri date to Gregorian and return in ISO format
local function hijriToGregorianISO(hijriYear, hijriMonth, hijriDay)
-- Constants for Solar Hijri Calendar (Persian Calendar)
local baseYearGregorian = 621 -- Base year for Hijri-Gregorian correspondence
local hijriNewYearOffset = 79 -- Offset for Persian New Year around March 21
-- Calculate the Gregorian year
local gregorianYear = hijriYear + baseYearGregorian
local approximateGregorianDayOfYear = (hijriMonth - 1) * 30 + hijriDay + hijriNewYearOffset
-- Adjust to convert the day and month accurately
local day = approximateGregorianDayOfYear
local month = 3 -- Start around March
while day > 30 do
day = day - 30
month = month + 1
if month > 12 then
month = 1
gregorianYear = gregorianYear + 1
end
end
-- Format the date in ISO format (YYYY-MM-DD)
local isoDate = string.format("%04d-%02d-%02d", gregorianYear, month, day)
return isoDate
end
function p.officialWebsiteLanguageLinks(frame)
local qid = frame.args.qid
if not qid then return "" end
local entity = mw.wikibase.getEntity(qid)
if not entity or not entity.claims then
return ""
end
local fallbackLabel = mw.wikibase.getLabel(qid)
local links = {}
-- === Existing P856 logic ===
if entity.claims.P856 then
for _, claim in ipairs(entity.claims.P856) do
local snak = claim.mainsnak
if snak and snak.datavalue then
local url = snak.datavalue.value
local label = nil
-- Preferred: language of work (P407)
if claim.qualifiers and claim.qualifiers.P407 then
local langSnak = claim.qualifiers.P407[1]
if langSnak and langSnak.datavalue then
label = mw.wikibase.getLabel(langSnak.datavalue.value.id)
end
end
-- Fallback: item label
if not label then
label = fallbackLabel
end
if label then
table.insert(
links,
string.format("[%s %s]", url, label)
)
end
end
end
end
local output = {}
-- Add P856 links row if any
if #links > 0 then
local icon = '🌐 '
table.insert(output, '<div class="boxrow">' .. icon .. table.concat(links, " • ") .. '</div>')
end
-- === Enhanced P1325 logic ===
local p1325links = {}
if entity.claims.P1325 then
for _, claim in ipairs(entity.claims.P1325) do
local snak = claim.mainsnak
if snak and snak.datavalue then
local url = snak.datavalue.value
local labels = {}
-- Collect all P407 qualifier labels
if claim.qualifiers and claim.qualifiers.P407 then
for _, qual in ipairs(claim.qualifiers.P407) do
if qual.datavalue then
local langLabel = mw.wikibase.getLabel(qual.datavalue.value.id)
if langLabel then
table.insert(labels, langLabel)
end
end
end
end
-- Determine link text based on number of P407 qualifiers
local linkText
if #labels == 0 then
-- No P407: just "Data"
linkText = string.format("[%s Elements]", url)
elseif #labels == 1 then
-- Single P407
linkText = string.format("[%s %s]", url, labels[1])
else
-- Multiple P407
linkText = string.format("[%s %s]", url, table.concat(labels, " / "))
end
table.insert(p1325links, linkText)
end
end
end
-- Add P1325 links row if any
if #p1325links > 0 then
local icon = '🗂️ '
table.insert(output, '<div class="boxrow">' .. icon .. table.concat(p1325links, " • ") .. '</div>')
end
return table.concat(output)
end
function p.originStats(frame)
-- Everything lives inside this function
local sparql = require('mw.wikibase.sparql')
local args = frame.args
local countryQid = args[1] or args.country
if not countryQid or countryQid == '' then
return ''
end
local query = string.format([[
SELECT
(COUNT(DISTINCT ?item) AS ?items)
(COUNT(?rlStatement) AS ?rls)
(COUNT(?uslStatement) AS ?usls)
(COUNT(?bspStatement) AS ?bsps)
(COUNT(DISTINCT ?invStatement) AS ?invs)
WHERE {
?item p:P3259 ?p3259 .
FILTER EXISTS {
?item
wdt:P17
| wdt:P495
| wdt:P2341
| p:P3259/p:P17
| p:P3259/ps:P3259/wdt:P17
wd:%s .
}
FILTER NOT EXISTS { ?p3259 wikibase:rank wikibase:DeprecatedRank }
OPTIONAL { ?p3259 ps:P3259 wd:Q110319947 . BIND(?p3259 AS ?rlStatement) }
OPTIONAL { ?p3259 ps:P3259 wd:Q17323370 . BIND(?p3259 AS ?uslStatement) }
OPTIONAL { ?p3259 ps:P3259 wd:Q17325849 . BIND(?p3259 AS ?bspStatement) }
OPTIONAL {
?p3259 ps:P3259 ?inv .
FILTER(?inv NOT IN (wd:Q110319947, wd:Q17323370, wd:Q17325849))
BIND(?p3259 AS ?invStatement)
}
}
]], countryQid)
local data = sparql.select(query)
if not data or not data[1] then
return ''
end
local row = data[1]
-- Expand the existing template with numeric values
return frame:expandTemplate{
title = 'Wiki_Loves_Living_Heritage/Originstats',
args = {
items = tonumber(row.items) or 0,
rls = tonumber(row.rls) or 0,
usls = tonumber(row.usls) or 0,
bsps = tonumber(row.bsps) or 0,
invs = tonumber(row.invs) or 0
}
}
end
-- Once you've finished adding functions to 'p', you need to return it.
return p