Модуль используется в шаблоне {{Внешние ссылки}}. Список внешних ссылок см. на Модуль:External links/data. Примеры использования для тестов см. в Модуль:External links/песочница.


local data = require('Module:External links/data')
-- Localizable part
-- Please, note, that labels to various sites and cataloges are taken from Wikidata (i.e. Wikidata label)

local linksPrefix = ''
local project = 'Викиучебник'
local categoryTemplateEmpty = project .. ':Шаблон «Внешние ссылки» пуст'
local categoryWithWikimediaCommons = project .. ':Статьи со ссылками на Викисклад'
local templateLink = 'Внешние ссылки'

local group1Label = '[[' .. linksPrefix .. 'Социальная сеть|В социальных сетях]]'
local group2Label = 'Тексты произведений'
local group3Label = 'Фото, видео и аудио'
local group4Label = 'Тематические сайты'
local group5Label = 'Словари и энциклопедии'
local group6Label = 'Генеалогия и некрополистика'
local group7Label = 'Таксономия'
local group8Label = '[[' .. linksPrefix .. 'Нормативный контроль|Нормативный контроль]]'

-- The language codes that should be always displayed even if they have normal rank and claim with another language and prefferered rank exists
local preferredLanguage = 'Q7737'; -- russian

local templateColorName = 'цвет';
-- Some projects have "named" colors, defined by templates
function colorByTitle( frame, colorTitle )
	local templateName = 'Цвет/' .. colorTitle;
	local templateTitle = mw.title.makeTitle( 'Template', templateName );
	if ( templateTitle == nil or not templateTitle.exists ) then
		return false;
	end
	return frame:expandTemplate{ title = templateName };
end

-- Feel free to correct labels and categories, or add/remove sources here

-- Non-localizable part (not need to localize )
local moduleNavbox = require('Module:Navbox')
local moduleLanguages -- accessed if necessary

local titleBasedLinks = { ['Q602358'] = true, ['Q17290934'] = true, ['Q1960551'] = true }

local p = {}

function link( url )
	return url
end

function renderLabel( params )
	if type( params ) == 'string' then
		return params;
	end

	local id = params[ 1 ];
	local default = params[ 2 ];

	if #params >= 3 then
		local label = params[ 3 ];
		local link = mw.wikibase.sitelink( id );
		if ( link ~= nil ) then
			return '[[' .. link .. '|' .. label .. ']]';
		end
		local title = mw.wikibase.label( id ) or default;
		return '<span title="' .. title .. '" style="border-bottom: 1px dotted; cursor: help;">' .. label .. '</span>'
	end

	return mw.wikibase.label( id ) or default;
end

function getQualifierSingleValue( statement, qualifierName )
	if (statement ~= nil
			and statement.qualifiers ~= nil
			and statement.qualifiers[qualifierName] ~= nil) then

		for qualifierIndex, qualifier in pairs( statement.qualifiers[qualifierName] ) do
			if (qualifier.datavalue ~= nil
					and qualifier.datavalue.type ~= nil
					and qualifier.datavalue.value ~= nil) then

				if ( qualifier.datavalue.type == "monolingualtext" ) then
					return qualifier.datavalue.value.text;
				end
				if ( qualifier.datavalue.type == "string" ) then
					return qualifier.datavalue.value;
				end
				if ( qualifier.datavalue.type == "wikibase-entityid" ) then
					return qualifier.datavalue.value.id;
				end
				mw.log( 'Unknown qualifier type: ' .. qualifier.datavalue.type )
				return qualifier.datavalue.value;

			end
		end

	end
	return nil;
end

function getQualifierValues( statement, qualifierName )
	local result = {}
	if (statement ~= nil
			and statement.qualifiers ~= nil
			and statement.qualifiers[qualifierName] ~= nil) then
		local qualifiers = statement.qualifiers[qualifierName];
		for _, qualifier in pairs( qualifiers ) do
			if (qualifier.datavalue ~= nil
				and qualifier.datavalue.type ~= nil
				and qualifier.datavalue.value ~= nil) then

				if ( qualifier.datavalue.type == "string" ) then
					result[ #result + 1 ] = qualifier.datavalue.value;
				elseif ( qualifier.datavalue.type == "wikibase-entityid" ) then
					result[ #result + 1 ] = qualifier.datavalue.value.id;
				else
					mw.log( 'Unknown qualifier type: ' .. qualifier.datavalue.type );
					result[ #result + 1 ] = qualifier.datavalue.value;
				end
			end
		end
	end
	return result;
end

function collectLinks( configuration, elementId )
	--Create rows
	local elements = {}
	local data = {}


	local item = mw.wikibase.getEntity( elementId )
	if item == nil or item.claims == nil then
		return elements
	end

	if ( item.claims['P553'] ~= nil ) then
		local claim = item.claims['P553']
		for _, statement in pairs( claim ) do
			if (statement ~= nil) then
				-- profile ID
				local rank = statement.rank or 'normal';
				if ( rank ~= 'deprecated' ) then
					local itemId = getQualifierSingleValue( statement, 'P554' );
					if (itemId ~= nil) then
						-- language
						local languages = getQualifierValues( statement, 'P407' );
						local resourceId = statement.mainsnak.datavalue.value.id;
						if (data[resourceId] == nil) then
							data[resourceId] = {};
						end
						table.insert( data[resourceId], { itemId = itemId, languages = languages, rank = rank} );
					end
				end
			end
		end
	end

	for _, params in pairs( configuration ) do
		local resourceId = params[2]

		local claim = item.claims[ resourceId ]
		if ( claim ) then
			for _, statement in pairs( claim ) do
				local rank = statement.rank or 'normal';
				if ( rank ~= 'deprecated' and statement.mainsnak.datavalue) then
					local itemId = statement.mainsnak.datavalue.value;
					local languages = getQualifierValues( statement, 'P407' );
					if (data[resourceId] == nil) then
						data[resourceId] = {};
					end
					table.insert( data[resourceId], { itemId = itemId, languages = languages, rank = rank} );
				end
			end
		end
	end

	for resourceId, resourceDatas in pairs( data ) do
		data[resourceId] = filterByRank( resourceDatas );
	end

	local hasNonOptionalLinks = false

	for _, params in pairs( configuration ) do
		local resourceId = params[2]
		local optional = params[5] or false;

		local resourceDatas = data[resourceId];
		if resourceDatas ~= nil then
			if ( not optional ) then
				hasNonOptionalLinks = true
			end

			local resourceLabel = renderLabel( params[1] );
			local firstChar = mw.ustring.sub( resourceLabel, 1, 1 );
			local separateDesign = firstChar == '[' or firstChar == '<';

			local html = '';
			if ( separateDesign ) then
				html = html .. resourceLabel .. ':&nbsp;';
			end

			local preitemId
			for index, resourceData in pairs(resourceDatas) do
				local itemId = resourceData.itemId;
				if index == 2 then
					--даёт возможность поставить id из одного свойства в разные ссылки
					if itemId == preitemId then
						break
					end
				end

				local languages = resourceData.languages;
				local link = params[3] ( itemId );
				local linkFirstChar;
				local interwiki;
				if ( link ) then
					linkFirstChar = mw.ustring.sub( link, 1, 1 );
					interwiki = linkFirstChar == ':'
				end
				if ( separateDesign ) then
					if ( index ~= 1 ) then
						html = html .. ',&nbsp;'
					end
					if ( link ) then
						if ( interwiki ) then
							html = html .. '[[' .. link .. '|' .. itemId .. ']]';
						else
							html = html .. '[' .. link .. ' ' .. itemId .. ']';
						end
					else
						html = html .. itemId;
					end
				else
					if ( index ~= 1 ) then
						html = html .. ' · '
					end
					if ( link ) then
						if ( interwiki ) then
							html = html .. '[[' .. link .. '|' .. resourceLabel .. ']]';
						else
							html = html .. '[' .. link .. ' ' .. resourceLabel .. ']';
						end
					else
						-- it should not happen
						html = html .. resourceLabel .. ':&nbsp;' .. itemId;
					end

					if ( languages ~= nil and #languages > 0 ) then
						if moduleLanguages ~= false then -- not false, but maybe nil
							if ( mw.title.makeTitle( 'Module', 'Languages' ).exists
									and mw.title.makeTitle( 'Module', 'Languages/data' ).exists
									and mw.title.makeTitle( 'Module', 'Wikidata/Language-codes' ).exists) then
								moduleLanguages = require('Module:Languages');
							else
								moduleLanguages = false;
							end
						end
						
						if ( moduleLanguages ) then
							for langIndex, language in pairs(languages) do
								html = html .. '&nbsp;' .. moduleLanguages.getRefHtml( language )
							end
						end
					end
				end
				preitemId = resourceData.itemId;
			end
			if ( #params >= 4 and params[4] ) then
				html = html .. '[[Category:' .. params[4] .. ']]'
			end
			table.insert( elements, html )
		end
	end

	if ( not hasNonOptionalLinks ) then
		return {}
	end

	return elements
end

function collectDictionaryLinks( elementId )
	--Create rows
	local elements = {}

	local item = mw.wikibase.getEntity( elementId );
	if ( item == nil or item.claims == nil) then
		return elements
	end

	local sourceToElementLinks = {};

	local claim = item.claims['P1343']
	if ( claim ) then
		for _, statement in pairs( claim ) do
			if (statement ~= nil) then
				local rank = statement.rank or 'normal';
				if ( rank ~= 'deprecated' ) then
					local resourceId = statement.mainsnak.datavalue.value.id;
					local languages = getQualifierValues( statement, 'P407' );

					-- Wikisource link ?
					local entityId = getQualifierSingleValue( statement, 'P805' ) or getQualifierSingleValue( statement, 'P248' );
					if ( entityId ) then
						if (sourceToElementLinks[resourceId] == nil) then
							sourceToElementLinks[resourceId] = {};
						end
						table.insert( sourceToElementLinks[resourceId], { entityId = entityId, languages = languages, rank = rank } );
					end

					-- URL to encyclopedia
					local url = getQualifierSingleValue( statement, 'P953' );
					if (url == nil) then
						-- no longer recommend, but widely used
						url = getQualifierSingleValue( statement, 'P854' ); 
					end
					if ( url ~= nil ) then
						if (sourceToElementLinks[resourceId] == nil) then
							sourceToElementLinks[resourceId] = {};
						end
						table.insert( sourceToElementLinks[resourceId], { url = url, languages = languages, rank = rank } );
					end
				end
			end
		end
	end

	for _, description in pairs( data.dictionaries ) do
		if ( description.linkF ) then
			local claim = item.claims[ description.id ];
			if ( claim ) then
				for _, statement in pairs( claim ) do
					local rank = statement.rank or 'normal';
					if ( rank ~= 'deprecated' and statement.mainsnak.datavalue) then
						local value = statement.mainsnak.datavalue.value;
						local url = description.linkF( value );
						local languages = getQualifierValues( statement, 'P407' );
						if ( sourceToElementLinks[description.id] == nil) then
							sourceToElementLinks[description.id] = {};
						end
						table.insert( sourceToElementLinks[description.id], { url = url, languages = languages, rank = rank} );
					end
				end
			end
		end
	end

	local html = '';
	for _, description in pairs( data.dictionaries ) do
		local links = sourceToElementLinks[ description.id ];
		if ( links ) then
			for _, link in pairs( links ) do
				if ( link.url ) then
					table.insert( elements, '[' .. link.url .. ' ' .. description.title .. ']' );
				end

				if ( link.entityId ) then
					local sitelink = mw.wikibase.getSitelink( link.entityId, description.project );
					if ( sitelink ) then
						table.insert( elements, '[[' ..  description.projectCode .. sitelink .. '|' .. description.title .. ']]' );
					end
				end
			end
		end
	end

	return elements
end

function contains( tableStructure, value )
	if ( tableStructure == nil or value == nil) then
		return true;
	end
	for index, line in pairs( tableStructure ) do
		if ( line == value ) then
			return true;
		end
	end
	return false;
end

function filterByRank( resourceDatas )
	-- itemId, languages. rank = rank

	local hasPreffered = false;
	for index, resourceData in pairs(resourceDatas) do
		if ( resourceData.rank == 'preferred' ) then
			hasPreffered = true;
		end
	end

	if (not hasPreffered) then
		return resourceDatas;
	end

	local result = {};
	for index, resourceData in pairs(resourceDatas) do
		if ( resourceData.rank == 'preferred' or contains(resourceData.languages, preferredLanguage) ) then
			table.insert(result, resourceData);
		end
	end

	return result;
end

function p.render( frame )
	local colorArg = '';
	local elementId = nil;
	if ( frame ~= nil ) then
		local parentArgs = frame:getParent().args
		colorArg = parentArgs[templateColorName] or parentArgs['color'] or parentArgs[1] or '';
		if parentArgs['from'] and parentArgs['from'] ~= '' then
			elementId = string.upper( parentArgs['from'] );
		elseif parentArgs['d'] and parentArgs['d'] ~= '' then
			elementId = string.upper( parentArgs['d'] );
		end
		if ( colorArg ~= '' ) then
			local firstChar = mw.ustring.sub( colorArg, 1, 1 );
			if ( firstChar ~= '#' ) then
				local byTemplate = colorByTitle( frame, colorArg );
				if ( byTemplate ) then
					colorArg = byTemplate;
				end
			end
		end
	end

	local navboxData = {
		name  = 'External links',
		navboxclass = 'navbox ruwikiArticleExternalLinksTable',
		bodyclass = 'hlist',
	};
	if colorArg and colorArg ~= '' then
		navboxData.groupstyle = 'background: ' .. colorArg .. ';';
	end

	local rowIndex = 1;

	local socialNetworksElements = collectLinks( data.socialNetworkProperties, elementId );
	if ( #socialNetworksElements > 0 ) then
		navboxData['group' .. rowIndex] = group1Label;
		navboxData['list' .. rowIndex] = table.concat( socialNetworksElements , ' · ' );
		rowIndex = rowIndex + 1;
	end

	local textsElements = collectLinks( data.textsProperties, elementId );
	if ( #textsElements > 0 ) then
		navboxData['group' .. rowIndex] = group2Label;
		navboxData['list' .. rowIndex] = table.concat( textsElements , ' · ' );
		rowIndex = rowIndex + 1;
	end

	local contentHostingElements = collectLinks( data.contentHostingProperties, elementId );
	if ( #contentHostingElements > 0 ) then
		navboxData['group' .. rowIndex] = group3Label;
		navboxData['list' .. rowIndex] = table.concat( contentHostingElements , ' · ' );
		rowIndex = rowIndex + 1;
	end

	local themeProfilesElements = collectLinks( data.themeProfilesProperties, elementId );
	if ( #themeProfilesElements > 0 ) then
		navboxData['group' .. rowIndex] = group4Label;
		navboxData['list' .. rowIndex] = table.concat( themeProfilesElements , ' · ' );
		rowIndex = rowIndex + 1;
	end

	local dictionaryElements = collectDictionaryLinks( elementId );
	if ( #dictionaryElements > 0 ) then
		navboxData['group' .. rowIndex] = group5Label;
		navboxData['list' .. rowIndex] = table.concat( dictionaryElements , ' · ' );
		rowIndex = rowIndex + 1;
	end

	local geniElements = collectLinks( data.geniGraves, elementId );
	if ( #geniElements > 0 ) then
		navboxData['group' .. rowIndex] = group6Label;
		navboxData['list' .. rowIndex] = table.concat( geniElements , ' · ' );
		rowIndex = rowIndex + 1;
	end

	local taxElements = collectLinks( data.taxons, elementId );
	if ( #taxElements > 0 ) then
		navboxData['group' .. rowIndex] = group7Label;
		navboxData['list' .. rowIndex] = table.concat( taxElements , ' · ' );
		rowIndex = rowIndex + 1;
	end

	local authorityControlElements = collectLinks( data.authorityControl, elementId );
	local authorityControlExtElements = collectLinks( data.authorityControlExt, elementId );
	if ( #authorityControlElements > 0 ) then
		navboxData['group' .. rowIndex] = group8Label;
		if ( #authorityControlExtElements > 0 ) then
			navboxData['list' .. rowIndex] = table.concat( authorityControlElements , ' · ' ) .. ' · ' .. table.concat( authorityControlExtElements , ' · ' );
		else
			navboxData['list' .. rowIndex] = table.concat( authorityControlElements , ' · ' );
		end
		if ( #authorityControlElements > 5 ) then
			navboxData['group' .. rowIndex] = nil;
			package.loaded['Module:Navbox'] = nil;
			local templateStyles = frame:extensionTag{ name = 'templatestyles', args = { src = 'Шаблон:Навигационная таблица/styles.css' } };
			local collapsibleNavbox = require('Module:Navbox')._navbox( { title = group8Label, list1 = navboxData['list' .. rowIndex],
				border = 'subgroup', navbar = 'plain', state = 'collapsed', titleclass = 'ts-navbox-plaintitle', bodyclass = 'authoritycontrol',
				titlestyle = navboxData.groupstyle, bodystyle = 'text-align: left;' } );
			navboxData['list' .. rowIndex] = templateStyles .. collapsibleNavbox;
		end
		rowIndex = rowIndex + 1;
	end

	if ( rowIndex == 1 ) then
		if ( mw.title.getCurrentTitle().namespace == 0 ) then
			return '[[Category:' .. categoryTemplateEmpty .. ']]';
		end
	else
		if navboxData['group1'] then
			navboxData['group1'] = '<div style="padding: 0px 18px 0px 0px; width: 100%;"><div style="float: left;">' ..
				frame:expandTemplate{ title = 'tnavbar-view', args = { templateLink } } .. '</div>&nbsp;&nbsp;' ..
				navboxData['group1'] .. '</div>';
		else
			navboxData['group1'] = '<div style="padding: 0px 0px 0px 0px; width: 100%;">' ..
				frame:expandTemplate{ title = 'tnavbar-view', args = { templateLink } } .. '</div>';
		end
	end

	local navbox = moduleNavbox._navbox( navboxData )
	return navbox
end

function p.renderDocumentation()
	local result = ''
	result = result .. '|-\n';
	result = result .. '! colspan=4 | ' .. group1Label .. '\n';
	result = result .. '|-\n';
	result = result .. renderDocumentationCategory( data.socialNetworkProperties );
	result = result .. '|-\n';
	result = result .. '! colspan=4 | ' .. group2Label .. '\n';
	result = result .. '|-\n';
	result = result .. renderDocumentationCategory( data.textsProperties );
	result = result .. '|-\n';
	result = result .. '! colspan=4 | ' .. group3Label .. '\n';
	result = result .. '|-\n';
	result = result .. renderDocumentationCategory( data.contentHostingProperties );
	result = result .. '|-\n';
	result = result .. '! colspan=4 | ' .. group4Label .. '\n';
	result = result .. '|-\n';
	result = result .. renderDocumentationCategory( data.themeProfilesProperties );
	result = result .. '|-\n';
	result = result .. '! colspan=4 | ' .. group5Label .. '\n';
	result = result .. '|-\n';
	result = result .. renderDocumentationCategory( data.dictionaries );
	result = result .. '|-\n';
	result = result .. '! colspan=4 | ' .. group6Label .. '\n';
	result = result .. '|-\n';
	result = result .. renderDocumentationCategory( data.geniGraves );
	result = result .. '|-\n';
	result = result .. '! colspan=4 | ' .. group7Label .. '\n';
	result = result .. '|-\n';
	result = result .. renderDocumentationCategory( data.taxons );
	result = result .. '|-\n';
	result = result .. '! colspan=4 | ' .. group8Label .. '\n';
	result = result .. '|-\n';
	result = result .. renderDocumentationCategory( data.authorityControl );
	return result;
end

function renderDocumentationCategory( links )
	local result = '';

	for _, params in pairs( links ) do
		local resourceLabel = renderLabel( params[ 1 ] or params.title );
		local resourceId = params[ 2 ] or params.id;
		local category = params[ 4 ];
		local optional;
		if ( params[ 5 ] or false ) then
			optional = 'TRUE';
		else
			optional = 'FALSE';
		end
	
		result = result .. '| ' .. resourceLabel .. '\n';
		if string.match( resourceId, '^P' ) then
			result = result .. '| [[:d:Property:' .. resourceId .. '|' .. resourceId .. ']]\n';
		elseif string.match( resourceId, '^Q' ) then
			result = result .. '| [[:d:' .. resourceId .. '' .. '|' .. resourceId .. ']]\n';
		else
			result = result .. '| &nbsp; \n';
		end

		if ( category ~= nil and category ~= false ) then
			result = result .. '| [[:Category:' .. category .. '|' .. category .. ']]\n';
		else
			result = result .. '| &nbsp; \n';
		end
		result = result .. '| ' .. optional .. '\n';
		result = result .. '|-\n';
	end

	return result;
end

return p