Documentation for this module may be created at ሞድዩል:Authority control/doc

require('Module:No globals')

local function cleanLink ( link, style )
	-- similar to mw.uri.encode
	local wikiLink = link
	if style == 'PATH' then
		wikiLink = mw.ustring.gsub( wikiLink, ' ', '%%%%20' )
	elseif style == 'WIKI' then
		wikiLink = mw.ustring.gsub( wikiLink, ' ', '_' )
	else -- if style == 'QUERY' then -- default
		wikiLink = mw.ustring.gsub( wikiLink, ' ', '+' )
	end
	wikiLink = mw.ustring.gsub( wikiLink, '%[', '%%5B' )
	wikiLink = mw.ustring.gsub( wikiLink, '%]', '%%5D' )
	wikiLink = mw.ustring.gsub( wikiLink, '%"', '%%%%22' )
	return wikiLink
end

local function generic ( id, link, parameter )
	local idlink = cleanLink( id, 'PATH' )
	link = mw.ustring.gsub( link, '$1', idlink )
	return '[' .. link .. ' ' .. id .. ']'
end

local function noLink ( id, link, parameter )
	-- avoid generating an external link together with the identifier
	return id
end

local function commonscat ( id, link, parameter )
	-- special representation of the link to the Commons categories, to maintain the interwiki link format
	local idlink = cleanLink( id, 'WIKI' )
	link = mw.ustring.gsub( link, '$1', idlink )
	return '<span class="plainlinks">[' .. link .. ' ' .. id .. ']</span>'
end

local function sisterprojects ( id, link, parameter )
	-- interproject links
	local prefix = {
		-- Example: -- enwiki = 'w:en',
		commonswiki = 'c',
		enwikivoyage = 'voy',
		tiwiktionary = 'wikt',
		enwikibooks = 'b',
		enwikinews = 'n',
		enwikiversity = 'v',
		enwikiquote = 'q',
		enwikisource = 's',
		mediawikiwiki = 'mw',
		metawiki = 'm',
		specieswiki = 'species'
	}
	if prefix[ parameter ] then
		return '[['..prefix[ parameter ]..':'..id..'|'..id..']]'
	end
	return false
end

function getCommonsValue ( itemId )
	local commonslink = ''
	local categories = ''

	local property = getIdsFromWikidata( itemId, 'P373' )
	if property and property[1] then
		property = property[1]
		commonslink = commonslink .. getLink( 373, property, commonscat )
	else
		property = ''
	end

	local sitelink = getIdsFromSitelinks( itemId, 'commonswiki' )
	if sitelink and sitelink[1] then
		sitelink = sitelink[1]
		if sitelink ~= 'Category:' .. property then
			if commonslink == '' then
				commonslink = commonslink .. sisterprojects( sitelink, nil, 'commonswiki' )
			end
		end
	else
		sitelink = ''
	end

	if property and sitelink then
		if sitelink ~= 'Category:' .. property then
			-- categories = categories .. '[[መደብ:ዊኪፐድያ:Authority control with non-Commons links]]'
		end
	elseif sitelink then -- not property
		-- categories = categories .. '[[መደብ:ዊኪፐድያ:Authority control without Commonscat]]'
	elseif property then -- not sitelink
		-- categories = categories .. '[[መደብ:ዊኪፐድያ:Authority control without Commons]]'
	else -- not property and not sitelink
		-- categories = categories .. '[[መደብ:ዊኪፐድያ:Authority control without any Commons link]]'
	end
	if commonslink ~= '' then
		-- Special:MediaSearch
		local mediasearch = 'https://commons.wikimedia.org/wiki/Special:MediaSearch?type=image&search=%22$1%22'
		commonslink = commonslink .. ' / ' .. commonscat( itemId, mediasearch )

		return { commonslink .. categories }
	end
	return {}
end

local conf = {}
--In this order: name of the parameter, label, propertyId in Wikidata, formatting function, category id
-- -- name of the parameter: unique name
-- -- label: internal link in wiki format
-- -- propertyId in Wikidata: number without 'P' suffix
-- -- formatting function: one of these four options
-- -- -- local function (like 'generic')
-- -- -- string 'y' (yes), to show a default identifier 'ID'
-- -- -- string 'n' (no), to show the real identifier
-- -- -- any other string, to show this string as identifier ('id', 'url', 'link', ...)
-- -- category id: one of these tree options
-- -- -- number 0, to not add category
-- -- -- number 1, to add a category based on the name of the parameter
-- -- -- any string, to add a category based on this string
conf.databases = {}
conf.databases[1] =	{}
conf.databases[1].name = 'Authority control'
conf.databases[1].list = {
	{
		title = 'ፕሮጀክታት ዊኪሜድያ',
		group = {
			{ 'Wikidata', '[[ፋይል:Wikidata-logo.svg|20px|link=Wikidata|alt=Wd|Wikidata]] መዋፈሪ ሓበሬታ', 'Wikidata:$1', 'n', 0 },
			{ 'Commons', '[[ፋይል:Commons-logo.svg|15px|link=Wikimedia Commons|alt=Commonscat|Commonscat]] ሕብረ-ሜድያዊ', getCommonsValue, 'n', 0 },
			{ 'Wikivoyage', '[[ፋይል:Wikivoyage-logo.svg|15px|link=Wikivoyage|alt=Wikivoyage|Wikivoyage]] ሓባሪ ጉዕዞ', 'enwikivoyage', sisterprojects, 0 },
			{ 'Wiktionary', '[[ፋይል:Wiktionary-logo.svg|15px|link=Wiktionary|alt=Wiktionary|Wiktionary]] መዝገበ-ቃላት', 'tiwiktionary', sisterprojects, 0 },
			{ 'Wikibooks', '[[ፋይል:Wikibooks-logo.svg|15px|link=Wikibooks|alt=Wikibooks|Wikibooks]] መምሃሪ መጻሕፍትን መምርሒታትን', 'enwikibooks', sisterprojects, 0 },
			{ 'Wikinews', '[[ፋይል:Wikinews-logo.svg|20px|link=Wikinews|alt=Wikinews|Wikinews]] ዜና', 'enwikinews', sisterprojects, 0 },
			{ 'Wikiversity', '[[ፋይል:Wikiversity-logo.svg|15px|link=Wikiversity|alt=Wikiversity|Wikiversity]] መምሃሪ ሃብቲ', 'enwikiversity', sisterprojects, 0 },
			{ 'Wikiquote', '[[ፋይል:Wikiquote-logo.svg|15px|link=Wikiquote|alt=Wikiquote|Wikiquote]] ጥርናፈ ጥቕስታት', 'enwikiquote', sisterprojects, 0 },
			{ 'Wikisource', '[[ፋይል:Wikisource-logo.svg|15px|link=Wikisource|alt=Wikisource|Wikisource]] ቤተ-መጻሕፍቲ', 'enwikisource', sisterprojects, 0 },
			{ 'MediaWiki', '[[ፋይል:MediaWiki-2020-icon.svg|20px|link=MediaWiki|alt=MediaWiki|MediaWiki]] ሜድያዊኪ', 'mediawikiwiki', sisterprojects, 0 },
			{ 'Meta-Wiki', '[[ፋይል:Wikimedia Community Logo.svg|15px|link=Wikimedia Meta-Wiki|alt=Meta-Wiki|Meta-Wiki]] ምውህሃድ', 'metawiki', sisterprojects, 0 },
			{ 'Wikispecies', '[[ፋይል:Wikispecies-logo.svg|15px|link=Wikispecies|alt=Wikispecies|Wikispecies]] ዓይነታት', 'specieswiki', sisterprojects, 0 },
		},
	},
}
-- -- Example row: --
-- conf.databases[2] = {}
-- conf.databases[2].name = 'External links'
-- conf.databases[2].list = {
-- 	{
-- 		title = '',
-- 		group = {
-- 			{ 'Website', 'Website', 856, 'n', 0 },
-- 		},
-- 	},
-- }

--In this order: alternate name, name of parameter from databases table
conf.aliases = {
	{ 'Wd', 'Wikidata' },
	{ 'PND', 'GND' },
	{ 'Commonscat', 'Commons' },
}

local function getCatForId( parameter, category )
	local title = mw.title.getCurrentTitle()
	local namespace = title.namespace
	if category == 0 then
		return ''
	elseif category == 1 then
		category = parameter
	end
	if namespace == 0 then
		return '[[መደብ:ዊኪፐድያ:Articles with identifiers ' .. category .. ']]\n'
	elseif namespace == 2 and not title.isSubpage then
		return '[[መደብ:ዊኪፐድያ:User pages with identifiers ' .. category .. ']]\n'
	else
		return '[[መደብ:ዊኪፐድያ:Miscellaneous pages with identifiers ' .. category .. ']]\n'
	end
end

function getIdsFromSitelinks( itemId, property )
	local ids = {}
	local siteLink = itemId and mw.wikibase.getSitelink( itemId, property )
	if siteLink then
		table.insert( ids, siteLink )
	end
	return ids
end

function getIdsFromWikidata( itemId, property )
	local ids = {}
	local statements = mw.wikibase.getBestStatements(itemId, property)
	
	for _, statement in pairs( statements) do
		if statement.mainsnak.datavalue then
			table.insert( ids, statement.mainsnak.datavalue.value )
		end
	end
	return ids
end

function getLink( property, val, mask )
	local link = ''
	if mw.ustring.find( val, '//' ) then
		link = val
	else
		if type(property) == 'number' then
			local entityObject = mw.wikibase.getEntityObject('P'..property)
			local dataType = entityObject.datatype
			if dataType == 'external-id' then
				local allStatements = entityObject:getAllStatements('P1630')
				if allStatements then
					for pos = 1, #allStatements, 1 do
						local q = allStatements[pos].qualifiers
						if q and q.P407 and q.P407[1].datavalue.value.id == 'Q1321' then
							link = allStatements[pos].mainsnak.datavalue.value
						end
					end
				end
				if link == '' then
					local formatterURL = entityObject:getBestStatements('P1630')[1]
					if formatterURL then
						link = formatterURL.mainsnak.datavalue.value
					else
						local formatterURL = entityObject:getBestStatements('P3303')[1]
						if formatterURL then link = formatterURL.mainsnak.datavalue.value end
					end
				end
			elseif dataType == 'url' then
				local subjectItem = entityObject:getBestStatements('P1629')[1]
				if subjectItem then
					local officialWebsite = mw.wikibase.getBestStatements(subjectItem.mainsnak.datavalue.value.id, 'P856')[1]
					if officialWebsite then
						link = officialWebsite.mainsnak.datavalue.value 
					end
				end
			elseif dataType == 'string' then
				local formatterURL = entityObject:getBestStatements('P1630')[1]
				if formatterURL then
					link = formatterURL.mainsnak.datavalue.value
				else
					local formatterURL = entityObject:getBestStatements('P3303')[1]
					if formatterURL then
						link = formatterURL.mainsnak.datavalue.value
					else
						local subjectItem = entityObject:getBestStatements('P1629')[1]
						if subjectItem then
							local officialWebsite = mw.wikibase.getBestStatements(subjectItem.mainsnak.datavalue.value.id,'P856')[1]
							if officialWebsite then
								link = officialWebsite.mainsnak.datavalue.value
							end
						end
					end
				end
			end
		elseif type(property) == 'string' then
			link = property
		end
	end
	link = mw.ustring.gsub(link, '^[Hh][Tt][Tt][Pp]([Ss]?)&#58;//', 'http%1://') -- fix wikidata URL
	if type(mask) == 'function' then
		return mask( val, link, property )
	end
	link = mw.ustring.gsub(link, '$1', mw.ustring.gsub( mw.ustring.gsub( val, '%%', '%%%%' ), ' ', '%%%%20' ) or val )
	if mw.ustring.find( link, '//' ) then
		if type(mask) == 'string' then
			link = cleanLink( link, 'PATH' )
			if mask == 'y' then
				return '['..link..' ID]'
			elseif mask == 'n' then
				return '['..link..' '..val..']'
			end
			return '['..link..' '..mask..']'
		end
	elseif link == '' then
		return val
	else
		return '[['..link..'|'..val..']]'
	end
end

local function createRow( id, label, rawValue, link, withUid )
	if link then
		if label and label ~= '' then label = '<span style="white-space:nowrap;">'..label  .. ':</span> ' end
		if withUid then
			return '* ' .. label .. '<span class="uid">' .. link .. '</span>\n'
		else
			return '* ' .. label .. link .. '\n'
		end
	else
		return '* <span class="error">The ' .. id .. ' id ' .. rawValue .. ' is not valid</span>[[መደብ:ዊኪፐድያ:Pages with authority control issues]]\n'
	end
end

local function copyTable(inTable)
	if type(inTable) ~= 'table' then return inTable end
	local outTable = setmetatable({}, getmetatable(inTable))
	for key, value in pairs (inTable) do outTable[copyTable(key)] = copyTable(value) end
	return outTable
end

local function splitLccn( id )
	if id:match( '^%l%l?%l?%d%d%d%d%d%d%d%d%d?%d?$' ) then
		id = id:gsub( '^(%l+)(%d+)(%d%d%d%d%d%d)$', '%1/%2/%3' )
	end
	if id:match( '^%l%l?%l?/%d%d%d?%d?/%d+$' ) then
		return mw.text.split( id, '/' )
	end
	return false
end

local p = {}

function p.authorityControl( frame )
	local pArgs = frame:getParent().args
	local parentArgs = copyTable(pArgs)
	local stringArgs = false
	local fromForCount, itemCount, rowCount = 1, 0, 0
	local mobileContent = ''
	--Cleanup args
	for k, v in pairs( pArgs ) do
		if type(k) == 'string' then
			--make args case insensitive
			local lowerk = mw.ustring.lower(k)
			if not parentArgs[lowerk] or parentArgs[lowerk] == '' then
				parentArgs[lowerk] = v
				parentArgs[k] = nil
			end
			--remap abc to abc1
			if not mw.ustring.find(lowerk, '%d$') then --if no number at end of param
				if not parentArgs[lowerk..'1'] or parentArgs[lowerk..'1'] == '' then
					parentArgs[lowerk..'1'] = v
					parentArgs[lowerk] = nil
				end
			end
			if v and v ~= '' then
				--find highest from param
				if mw.ustring.sub(lowerk,1,4) == 'from' then
					local fromNumber = tonumber(mw.ustring.sub(lowerk,5,-1))
					if fromNumber and fromNumber >= fromForCount then fromForCount = fromNumber end
				elseif mw.ustring.sub(lowerk,1,3) == 'for' then
					local forNumber = tonumber(mw.ustring.sub(lowerk,4,-1))
					if forNumber and forNumber >= fromForCount then fromForCount = forNumber end
				elseif mw.ustring.lower(v) ~= 'no' and lowerk ~= 'for' then
					stringArgs = true
				end
			end
		end
	end

	--Setup navbox
	local navboxParams = {
		name  = 'Authority control',
		bodyclass = 'hlist',
		groupstyle = 'width: 12%; text-align:center;',
	}
	for f = 1, fromForCount, 1 do
		local title = {}
		--cleanup parameters
		if parentArgs['from'..f] == '' then parentArgs['from'..f] = nil end
		if parentArgs['for'..f] == '' then parentArgs['for'..f] = nil end
		--remap aliases
		for _, a in pairs( conf.aliases ) do
			local alias, name = mw.ustring.lower(a[1]), mw.ustring.lower(a[2])
			if parentArgs[alias..f] and not parentArgs[name..f] then
				parentArgs[name..f] = parentArgs[alias..f]
				parentArgs[alias..f] = nil
			end
		end

		--Fetch Wikidata item
		local itemId = parentArgs['from'..f] or mw.wikibase.getEntityIdForCurrentPage()
		local label = itemId and (mw.wikibase.getSitelink(itemId) or mw.wikibase.getLabel(itemId)) or ''
		if label and label ~= '' then
			title = mw.title.new(label)
			if not title then title = mw.title.getCurrentTitle() end
		else
			title = mw.title.getCurrentTitle()
		end

		if (not parentArgs['wikidata'..f] or parentArgs['wikidata'..f] == '') and (title.namespace == 0 or title.namespace == 104) then
			parentArgs['wikidata'..f] = parentArgs['from'..f] or itemId or ''
		end
		if title.namespace == 0 or title.namespace == 104 or stringArgs then --Only in the main namespaces or if there are manual overrides

			if fromForCount > 1 and #conf.databases > 1 then
				if parentArgs['for'..f] and parentArgs['for'..f] ~= '' then
					navboxParams['list'..(rowCount + 1)] = "'''" .. parentArgs['for'..f] .. "'''"
				else
					navboxParams['list'..(rowCount + 1)] = "'''" .. title.text .. "'''"
				end
				navboxParams['list'..(rowCount + 1)..'style'] = 'background-color: #ddf;'
				rowCount = rowCount + 1
			end
			for _, db in pairs( conf.databases ) do
				if db.list and #db.list > 0 then
					local listElements = {}
					for n, gr in pairs( db.list ) do
						local groupElements = {}
						if gr.group and #gr.group > 0 then
							for _, params in pairs( gr.group ) do
								local id = mw.ustring.lower( params[1] )
								-- Wikidata fallback if requested
								if itemId and params[3] ~= 0 and (not parentArgs[id..f] or parentArgs[id..f] == '') then
									local wikidataIds = {}
									if type( params[3] ) == 'function' then
										wikidataIds = params[3]( itemId )
									elseif type( params[3] ) == 'string' then
										wikidataIds = getIdsFromSitelinks(itemId, params[3] )
									else
										wikidataIds = getIdsFromWikidata( itemId, 'P' .. params[3] )
									end
									if wikidataIds[1] then
										parentArgs[id..f] = wikidataIds[1]
									end
								end
								-- Worldcat
								if id == 'issn' and parentArgs['worldcatid'..f] and parentArgs['worldcatid'..f] ~= '' then -- 'issn' is the first element following the 'wikidata' item
									table.insert( groupElements, createRow( id, '', parentArgs['worldcatid'..f], '[//www.worldcat.org/identities/' .. parentArgs['worldcatid'..f] .. ' WorldCat]', false ) ) --Validation?
								elseif id == 'viaf' and parentArgs[id..f] and string.match( parentArgs[id..f], '^%d+$' ) and not parentArgs['worldcatid'..f] then -- Hackishly copy the validation code; this should go away when we move to using P1793 and P1630
									table.insert( groupElements, createRow( id, '', parentArgs[id..f], '[//www.worldcat.org/identities/containsVIAFID/' .. parentArgs[id..f] .. ' WorldCat]', false ) )
								elseif id == 'lccn' and parentArgs[id..f] and parentArgs[id..f] ~= '' and not parentArgs['viaf'..f] and not parentArgs['worldcatid'..f] then
									local lccnParts = splitLccn( parentArgs[id..f] )
									if lccnParts and lccnParts[1] ~= 'sh' then
										table.insert( groupElements, createRow( id, '', parentArgs[id..f], '[//www.worldcat.org/identities/lccn-' .. lccnParts[1] .. lccnParts[2] .. '-' .. lccnParts[3] .. ' WorldCat]', false ) )
									end
								end

								local val = parentArgs[id..f]
								if val and val ~= '' and mw.ustring.lower(val) ~= 'no' and params[3] ~= 0 then
									local link
									if type( params[3] ) == 'function' then
										link = val
									else
										link = getLink( params[3], val, params[4] )
									end
									if link and link ~= '' then
										table.insert( groupElements, createRow( id, params[2], val, link, true ) .. getCatForId( params[1], params[5] or 0 ) )
										itemCount = itemCount + 1
									end
								end
							end
							if #groupElements > 0 then
								if gr.title and gr.title ~= '' then
									table.insert( listElements, "* '''"..gr.title.."'''\n" )
								end
								table.insert( listElements, table.concat( groupElements ) )
								if n == 1 and #groupElements > 1 then
									table.insert( listElements, "\n----\n" )
								end
								-- mobile version
								if n == 1 then
									mobileContent = table.concat( groupElements )
								end
							end
						end
					end
					-- Generate navbox title
					if #listElements > 0 then
						if fromForCount > 1 and #conf.databases == 1 then
							if parentArgs['for'..f] and parentArgs['for'..f] ~= '' then
								navboxParams['group'..(rowCount + 1)] = "''" .. parentArgs['for'..f] .. "''"
							else
								navboxParams['group'..(rowCount + 1)] = "''" .. title.text .. "''"
							end
						else
							navboxParams['group'..(rowCount + 1)] = db.name or ''
						end
						navboxParams['list'..(rowCount + 1)] = table.concat( listElements )
						rowCount = rowCount + 1
					end
				end
			end
		end
	end
	if rowCount > 0 then
		local Navbox = require('Module:Navbox')
		if fromForCount > 1 then
			--add missing names
			for r = 1, rowCount, 1 do
				if navboxParams['group'..r] == '' then
					navboxParams['group'..r] = "''" .. mw.wikibase.getEntity(parentArgs['wikidata'..r]):getLabel().."''"
				end
			end
			if fromForCount > 2 then
				navboxParams['navbar'] = 'plain'
			else
				navboxParams['state'] = 'off'
				navboxParams['navbar'] = 'off'
			end
		end
		local mainCategories = ''
		if stringArgs then
			mainCategories = mainCategories .. '[[Category:ዊኪፐድያ:Pages using authority control with parameters]]\n'
		end
		if itemCount > 13 then
			if itemCount > 30 then
				itemCount = 'more than 30'
			end
			mainCategories = mainCategories .. '[[Category:ዊኪፐድያ:Authority control with 3 ' .. itemCount .. ' elements]]\n'
		end
		navboxParams['style'] = 'width: inherit';
		return frame:extensionTag{ name = 'templatestyles', args = { src = 'Template:Authority control/styles.css' } }
			.. tostring(
				mw.html.create( 'div' )
					:addClass( 'mw-authority-control' )
					:wikitext( Navbox._navbox( navboxParams ) )
					:done()
				:tag('div')
					:addClass( 'mw-mf-linked-projects' )
					:addClass( 'hlist' )
					:newline()
					:wikitext( mobileContent )
					:done()
				:done()
			)
			.. mainCategories
	else
		return ''
	end
end

return p