Module:License

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
Lua

CodeDiscussionEditHistoryLinksLink count Subpages:DocumentationTestsResultsSandboxLive code All modules


Usage

Both statements are equivalent:

Examples

Usually {{License from structured data}} is added to a file page. However one can also test the module by using parameters: id (for SDC or Wikidata item number) and debug (a Boolean flag to turn off template rendering).

File Test {{License from structured data}}
Original license template
Self
+ cc-by-3.0
+ GFDL
+ attribution
{{self|Cc-by-3.0|GFDL|attribution=Jarek Tuszyński / Cc-by-3.0 / GFDL|author=Jarek Tuszyński}}
{{self|cc-by-3.0|GFDL|author=Jarek Tuszyński|attribution=Jarek Tuszyński / CC-BY-3.0 & GDFL}}
FAL
+ CeCILL
+ GFDL-1.2
{{self|CeCILL|FAL|GFDL-1.2|attribution=Ralf Roletschek / CeCILL / FAL / GFDL-1.2|author=Ralf Roletschek}}
{{GFDL-1.2}}{{CeCILL}}{{FAL}}
Cc-by-sa-2.0
+ attribution
{{Cc-by-sa-2.0|attribution=Derelict looking barn, New Pound by N Chadwick / Cc-by-sa-2.0|author=N Chadwick}}
{{Cc-by-sa-2.0|attribution=Derelict looking barn, New Pound by N Chadwick}}
Cc-by-2.0 {{Cc-by-2.0|attribution=Robin Taylor / Cc-by-2.0|author=Robin Taylor}}
{{Cc-by-2.0}}
cc-zero {{self|Cc-zero}}
{{Cc-zero}}
PD-USGov-USGS
{{PD-USGov}}
{{PD-USGov-USGS}}
OGL3 {{OGL3|attribution=Cpl Ian Houlding / OGL3|author=Cpl Ian Houlding}}
{{OGL3|1=Photo: Cpl Ian Houlding/MOD}}
FAL {{self|Cc-by-sa-3.0|FAL|attribution=spacebirdy / Cc-by-sa-3.0 / FAL|author=spacebirdy}}
{{self|Cc-by-sa-3.0|FAL}}
GPL {{self|Cc-by-sa-4.0|GPLv2+}}
{{self|cc-by-sa-4.0}}{{Free screenshot|{{GPL}}}}
Attribution {{Attribution only license}}
{{Attribution|nolink=Shiretoko-Shari Tourist Association}}
Cc-by-sa-4.0,3.0
,2.5,2.0,1.0
{{self|Cc-by-sa-4.0,3.0,2.5,2.0,1.0|GFDL|attribution=Agreco / Cc-by-sa-1.0 / Cc-by-sa-2.0 / Cc-by-sa-2.5 / Cc-by-sa-3.0 / Cc-by-sa-4.0 / GFDL|author=Agreco}}
{{self|GFDL|Cc-by-sa-4.0,3.0,2.5,2.0,1.0}}
WTFPL {{self|Cc-zero|PD-author|WTFPL}}
{{self|cc-zero|WTFPL}}
MIT {{MIT}}
{{MIT}}
PD-old-100-expired {{PD-old-100-expired}} {{PD-Art}}
{{PD-Art|PD-old-100-expired|deathyear=1903}}

Code

--[[  
  __  __           _       _        _     _                         
 |  \/  | ___   __| |_   _| | ___ _| |   (_) ___ ___ _ __  ___  ___ 
 | |\/| |/ _ \ / _` | | | | |/ _ (_) |   | |/ __/ _ \ '_ \/ __|/ _ \
 | |  | | (_) | (_| | |_| | |  __/_| |___| | (_|  __/ | | \__ \  __/
 |_|  |_|\___/ \__,_|\__,_|_|\___(_)_____|_|\___\___|_| |_|___/\___|
                                                                    
This module is intended to be the engine behind "Template:License from structured data"

Please do not modify this code without applying the changes first at 
"Module:License/sandbox" and testing at "Template:License/testcases".

Authors and maintainers:
* User:Jarekt - original version 
]]

-- =======================================
-- === Dependencies ======================
-- =======================================
local core       = require('Module:Core')
local sdc_author = require('Module:Information')._SDC_Author

-- =======================================
-- === Global variables ==================
-- =======================================
local P = { -- define all the special Ps and Qs used by the program and give them names
	instance_of =          'P31',   -- instance of
	copyright_license =    'P275',  -- copyright license
	determination_method = 'P459',  -- determination method 
	applies_to_part =      'P518',  -- applies to part 
	official_website =     'P856',  -- official website 
	full_work_URL =        'P953',  -- full work available at URL 
	jurisdiction =         'P1001', -- applies to jurisdiction
	main_template =        'P1424', -- main template
	title =                'P1476', -- title
	duration =             'P2047', -- duration
	author_name_str =      'P2093', -- author name string		
	relative_to =          'P2210', -- relative to
	copyright_status =     'P6216', -- copyright status
	file_source =          'P7482', -- source of file
	attribution_text =     'P8264', -- attribution text
}

local Q = {
	public_domain =        'Q19652',     -- public domain
	copyrighted =          'Q50423863',  -- copyrighted 
	copyrighted_pd =       'Q88088423',  -- copyrighted, dedicated to the public domain by copyright holder 
	own_work =             'Q66458942',  -- original creation by uploader 
	death_date =           'Q108697943', -- date of death of author(s)
} 

local suported_license_familys = {
   'Q284742',    -- Creative Commons license family
   'Q22169',     -- GNU Free Documentation License family
   'Q196294',    -- free license 
   'Q1156659',   -- OSI-approved license 
   'Q3943414',   -- free software license 
   'Q5975031',   -- copyleft free software license
   'Q25047642',  -- public domain equivalent license
}

local multi_license_templates = { -- use list instead of dictionary-like table to preserve order
	{'Cc-by-sa-4.0,3.0,2.5,2.0,1.0', {'Cc-by-sa-1.0', 'Cc-by-sa-2.0', 'Cc-by-sa-2.5', 'Cc-by-sa-3.0', 'Cc-by-sa-4.0'}}, -- 400k	
	{'Cc-by-sa-3.0,2.5,2.0,1.0',     {'Cc-by-sa-1.0', 'Cc-by-sa-2.0', 'Cc-by-sa-2.5', 'Cc-by-sa-3.0'}},                 -- 1.1M	
	{'Cc-by-sa-2.5,2.0,1.0',         {'Cc-by-sa-1.0', 'Cc-by-sa-2.0', 'Cc-by-sa-2.5'}},	                                -- 288k
	{'Cc-by-4.0,3.0,2.5,2.0,1.0',    {'Cc-by-1.0',    'Cc-by-2.0',    'Cc-by-2.5',    'Cc-by-3.0',    'Cc-by-4.0'}},	-- 500 uses		
	{'Cc-by-3.0,2.5,2.0,1.0',        {'Cc-by-1.0',    'Cc-by-2.0',    'Cc-by-2.5',    'Cc-by-3.0'}},	                -- 17k uses		
	{'PD-old-100-expired',           {'PD-old-100',   'PD-US-expired'}},
	{'PD-old-95-expired',            {'PD-old-95',    'PD-US-expired'}},
	{'PD-old-90-expired',            {'PD-old-90',    'PD-US-expired'}},
	{'PD-old-80-expired',            {'PD-old-80',    'PD-US-expired'}},
	{'PD-old-75-expired',            {'PD-old-75',    'PD-US-expired'}},
	{'PD-old-70-expired',            {'PD-old-70',    'PD-US-expired'}},
	{'PD-old-60-expired',            {'PD-old-60',    'PD-US-expired'}},
	{'PD-old-50-expired',            {'PD-old-50',    'PD-US-expired'}},
	{'PD-old-100-1996',              {'PD-old-100',   'PD-US-1996'}},
	{'PD-old-95-1996',               {'PD-old-95',    'PD-US-1996'}},
	{'PD-old-90-1996',               {'PD-old-90',    'PD-US-1996'}},
	{'PD-old-80-1996',               {'PD-old-80',    'PD-US-1996'}},
	{'PD-old-75-1996',               {'PD-old-75',    'PD-US-1996'}},
	{'PD-old-70-1996',               {'PD-old-70',    'PD-US-1996'}},
	{'PD-old-60-1996',               {'PD-old-60',    'PD-US-1996'}},
	{'PD-old-50-1996',               {'PD-old-50',    'PD-US-1996'}},
}	

local find_and_replace = {
	{'{{self|PD-author}}', '{{PD-self}}'}
}

-- ==================================================
-- === Generic Local functions ======================
-- ==================================================
-------------------------------------------------------------------------------
local function getProperty(entity, prop)
	return (core.parseStatements(entity:getBestStatements( prop ), nil) or {nil})[1]
end

-------------------------------------------------------------------------------
local function getAllProperties(entity, prop)
	return core.parseStatements(entity:getAllStatements( prop ), nil)
end

-------------------------------------------------------------------------------
local function getAllItemProperties(item, prop)
	return core.parseStatements(mw.wikibase.getAllStatements( item, prop ), nil)
end

-------------------------------------------------------------------------------
local function getItemProperty(item, prop)
	return (core.parseStatements(mw.wikibase.getBestStatements( item, prop ), nil) or {nil})[1]
end

-- ===========================================================================
local function union(set1, set2)
	-- find union of two sets
	for __, item2 in ipairs(set2) do
		table.insert(set1, item2)
	end		
	return set1
end

-- ===========================================================================
local function intersect(set1, set2)
	-- find intersect of two sets
	local intersct = {}
	for __, item1 in ipairs(set1) do
		for __, item2 in ipairs(set2) do
			if item1 == item2 then
				table.insert(intersct, item1)
				break
			end
		end		
	end
	return intersct
end

-- ===========================================================================
local function contains(element, list)
	for __, item in ipairs(list) do
		if item == element then
			return true
		end
	end	
	return false
end

-- ===========================================================================
local function difference(set1, set2)
	-- find  difference of two sets
	local diff = {}
	local found 
	for __, item1 in ipairs(set1) do
		found = false
		for __, item2 in ipairs(set2) do
			if item1 == item2 then
				found = true
				break
			end
		end	
		if not found then
			table.insert(diff, item1)	
		end
	end	
	return diff
end

-- ===========================================================================
local function filter_statements(statments, pvals)
	-- from a list of "statements" remove those there the main value is not in the set "pvals"
	local res = {}
	for _, statement in ipairs(statments) do
		local snak = statement.mainsnak
		if (snak.snaktype == "value" and contains(snak.datavalue.value.id, pvals)  and 
		    snak.datavalue.value and statement.rank ~= 'deprecated') then
			table.insert(res, statement) 
		end
	end
	return res
end

-- ===========================================================================
local function parse_snak(snak)
	local val = snak.datavalue.value
	if val.id then 
		val = val.id
	elseif val.text then
		val = val.text
	elseif val.amount then
		val = tonumber(val.amount)
	end
	return val
end

-- ===========================================================================
local function getQuals(statements, qual)
	-- get all the qualifiers "qual" from a list of "statements" 
	local res = {}
	for _, statement in ipairs( statements ) do
		if (statement.mainsnak.snaktype == "value" and statement.qualifiers and statement.qualifiers[qual]) then
			for _,  snak in ipairs( statement.qualifiers[qual] ) do
				if (snak.snaktype == "value" and snak.datavalue.value) then 
					table.insert(res, parse_snak(snak))
				end
			end
		end
	end
	return res
end

-- ===========================================================================
local function getQual(entity, prop, qual)
	-- get the first qualifier "qual" of a property "prop"
	local statements = entity:getBestStatements( prop )
	return (getQuals(statements, qual))[1]
end

-- ===========================================================================
local function starts_with(text, prefix)
	-- string function testing of strings "text" starts with "prefix"
    return string.sub(text,1,string.len(prefix))==prefix
end

-- ==================================================
-- === Copyright specific local functions ===========
-- ==================================================

-- ===========================================================================
local function getQuals2(statements, quals, main_prop)
--[[ get all the qualifiers P["qual"] from a list of "statements" 
Inputs:
  1: statements - output of entity:getBestStatements( prop )
  2: quals - list of strings with qualifier names
  3: P - look up table converting qualifier names to property numbers
]]
	local res = {}
	for _, statement in ipairs( statements ) do
		local bundle = {}
		for _, qual in ipairs( quals ) do
			local prop = P[qual]
			if (statement.mainsnak.snaktype == "value" and statement.qualifiers and statement.qualifiers[prop]) then
				for _,  snak in ipairs( statement.qualifiers[prop] ) do
					if (snak.snaktype == "value" and snak.datavalue.value) then 
						bundle[qual] = parse_snak(snak)
					end
				end
			end
		end
		local snak = statement.mainsnak
		if (snak.snaktype == "value") and (statement.rank ~= 'deprecated')  then
			bundle[main_prop] = parse_snak(snak) -- key is property name not code
		end
		table.insert(res, bundle) 
	end
	return res
end

-- ===========================================================================
--[[
Look through template list and see 
Inputs:
  1: bundles - list of dictionary-like data structures storing copyright "bundles".
		Each bundle can have different license for different copyright holder, 
		applying to different countries or part of the item.

Outputs:
  1: bundles - altered list 
]]
local function aggregate_multi_license_templates (bundles)
	if #bundles<2 then
		return bundles, ''
	end
	local license_tags = {}
	for __, bundle in ipairs(bundles) do
		table.insert(license_tags, bundle['template_name'])
		if bundle['attribution_text'] or bundle['applies_to_part'] then
			-- do not aggregate if individual attributions applies_to_part used
			--return bundles, 'attr'
		end
	end	
	local msg = {'.'}
	for _, rec in ipairs(multi_license_templates) do
		local parent_temp, template_list = rec[1], rec[2]
		local intrsct = intersect(license_tags, template_list)
		table.insert(msg, tostring(#intrsct))
		if #intrsct == #template_list then -- all license_tags are contained in lic_list -> remove them and replace with parent_temp 
			local bundles2 = {{['template_name'] = parent_temp}}
			for _, bundle in ipairs(bundles) do
				if not contains(bundle['template_name'], template_list) then
					table.insert(bundles2, bundle) -- keep other templates
				else
					bundles2[1]['copyright_status'] = bundle['copyright_status']
				end
			end	
			return bundles2, table.concat(msg, ',')
		end
	end
	return bundles, table.concat(msg, ',')
end

-- ===========================================================================
--[[
Build attribution string based on provided elements
Inputs:
  1: bundles - list of dictionary-like data structures storing copyright "bundles".
  2: author_str - author string (optional)
  3: title_str  - title string  (very optional)
  
Outputs:
  1: attribution string
]]
local function get_default_attribution(bundles, author_str, title_str)
	local attr_list, link = {}, nil
	if author_str and title_str then
		author_str = mw.ustring.format("''%s'' by %s", title_str, author_str)
	end
	if not author_str then
		return nil
	end
	table.insert(attr_list, author_str)
	for _, bundle in ipairs(bundles) do
		local lic = bundle['template_name']
		local url = bundle['website']
		if url then
			link = '<span class="plainlinks noprint">[' .. url .. ' ' .. lic .. ']</span>'
		else
			link = '[[Template:' .. lic .. '|' .. lic .. ']]'
		end
		table.insert(attr_list, link)
	end
	return table.concat(attr_list, ' / ')
end

-- ===========================================================================
--[[
add attribution and author parameters to a license tag template
Inputs:
  1: license - license tag string
  2: author - author string (can be nil)
  3: attribution - attribution string (can be nil)
  
Outputs:
  1: altered string with license template wikicode 
]]
local function add_to_license_tag(license, author, attribution)
	if attribution then
		license= string.gsub(license, '}}$', '|attribution=' .. attribution .. '}}') -- add attribution to {{self}}
	end
	if author then
		license = string.gsub(license, '}}$', '|author=' .. author .. '}}')       -- add author to {{self}}
	end
	return license
end

-- ===========================================================================
--[[
form wikicode for {{self}} template based on provided elements
Inputs:
  1: license_tags - list of license templates names
  2: author_str - author string (optional)
  3: attribution_str - attribution string (optional)
  
Outputs:
  1: string with wikicode for {{self}} template
]]
local function form_self_template(bundles, author_str, attribution_str)
	local license_seg, pd_seg, num_cr = {'self'}, {}, 0
	for _, bundle in ipairs(bundles) do
		local status = bundle['copyright_status']
		if status == Q['copyrighted'] then
			table.insert(license_seg, bundle.template_name)
			num_cr = num_cr + 1
		elseif status == Q['copyrighted_pd']  then
			table.insert(license_seg, bundle.template_name)
		elseif status == Q['public_domain'] then
			table.insert(pd_seg, '{{' .. bundle.template_name .. '}}')
		end
	end
	local license_tag = '{{' .. table.concat(license_seg, '|') .. '}}'
	if num_cr>0 then
		license_tag = add_to_license_tag(license_tag, author_str, attribution_str)
	end
	table.insert(pd_seg, 1, license_tag)
	return pd_seg
end

-- ===========================================================================
local function harvest_copyright_data(entity)
--	local P, Q = mind_your_Ps_and_Qs() -- give names to often used items and properties
	local quals = {'copyright_license', 'attribution_text', 'applies_to_part', 
				   'jurisdiction', 'determination_method'}
	
	-- collect both copyright_status and copyright_license statements
	local cs_statements = entity:getBestStatements( P['copyright_status'] )
	local cl_statements = entity:getBestStatements( P['copyright_license'] )
	-- keep only "copyrighted" statements and skip "Public domain" ones
	--cs_statements = filter_statements(cs_statements, {Q['copyrighted'], Q['copyrighted_pd']}) 
	local bundles
	if #cs_statements == 0  then
		return {} -- no copyright statements found
	elseif #cs_statements >= 1 and #cl_statements == 0 then
		-- Wikidata style 'bundles' organized as qualifiers of copyright_status
		bundles = getQuals2(cs_statements, quals, 'copyright_status')
	elseif #cs_statements == 1 and #cl_statements >= 1 then
		-- Commons style: single copyright_status and bunch of copyright_licenses, possibly with qualifiers
		bundles = getQuals2(cl_statements, quals, 'copyright_license')
		local cs = (core.parseStatements(cs_statements, nil) or {nil})[1]
		for __, bundle in ipairs(bundles) do
			bundle['copyright_status'] = cs 
		end	
	else
		return {}
	end
	return bundles
end
	
-- ===========================================================================
local function get_free_licenses(entity, args)
	-- Set error messages
	local error_msg = {
		bad_license      = 'license [[d:%s]] does not belong to a recognized license family', 
		missing_template = 'item [[d:%s]] is missing main template (P1424) property',
		missing_sitelink = 'missing sitelink to Commons for item [[d:%s]]',
		bad_sitelink     = 'sitelink to Commons for item %s does not point to a template, but to "%s"',
		bad_jurisdiction = 'jurisdiction of "determination_method" %s does not match jurisdiction of the item',
		issue = 'copyright_status = %s; license_item = %s; det_metchod = %s; jurisdiction = %s'
	}

	-- collect licenses
	-- license info can be either in "copyright license" property or as qualifiers of "copyright_status" property
	local bundles = harvest_copyright_data(entity)
	if not bundles or #bundles == 0  then
		return {} -- no copyright statements found
	end

	-- get commons templates based on license bundles
	local template_name, template_item
	for _, bundle in ipairs(bundles) do
		local license_item = bundle['copyright_license']
		local det_metchod  = bundle['determination_method']
		local status       = bundle['copyright_status']
		if contains(status, {Q['copyrighted'], Q['copyrighted_pd']}) and license_item then
			local instance_of = getAllItemProperties(license_item, P['instance_of'])
			bundle['website'] = getItemProperty(license_item, P['official_website'])
			if table.maxn(intersect(instance_of, suported_license_familys))==0 then
				return mw.ustring.format(error_msg['bad_license'], license_item)
			end
			-- The license (like Creative Commons Attribution-ShareAlike 4.0 International (Q18199165)))) should have a 
			-- topic's main template (P1424) (Template:Cc-by-sa-4.0 (Q15243492)) that links to a valid template on Commons.
			template_item = getItemProperty(license_item, P['main_template'])
			if not template_item then -- missing sitelink to Commons to Commons template
				return mw.ustring.format(error_msg['missing_template'], template_item)
			end
		elseif contains(status, {Q['public_domain'], Q['copyrighted_pd']}) and det_metchod then
			local jurisdiction = getItemProperty(det_metchod, P['jurisdiction'])
			if jurisdiction ~= bundle['jurisdiction'] then
				return mw.ustring.format(error_msg['bad_jurisdiction'], det_metchod)
			end
			template_item = getItemProperty(det_metchod, P['main_template'])
			if not template_item then -- missing sitelink to Commons to Commons template
				return mw.ustring.format(error_msg['missing_template'], det_metchod)
			end	
			--[[if not template_name then -- missing sitelink to Commons template
				local jurisdiction = getItemProperty(det_metchod, P['jurisdiction'])
				for duration in getQuals2(entity:getBestStatements(P['duration']), {'relative_to'}, P) do
					if duration['relative_to'] == Q['death_date'] then
					end
				end
			end	]]
		else
			-- return mw.ustring.format(error_msg['issue'], status or 'nil', license_item or 'nil', 
			--	det_metchod or 'nil', bundle['jurisdiction'] or 'nil')
			return mw.text.jsonEncode(bundle)
		end	

		template_name = mw.wikibase.getSitelink(template_item)
		if not template_name then -- missing sitelink to Commons to Commons template
			return mw.ustring.format(error_msg['missing_sitelink'], template_item)
		end	
		if not starts_with(template_name, 'Template:') then -- sitelink to Commons does not point to a template
			return mw.ustring.format(error_msg['bad_sitelink'], template_item, template_name)
		end
		bundle['template_name'] = string.gsub(template_name, '^Template:', '', 1) -- remove 'Template:' prefix
	end
	
	-- sort bundles 
	local sort_fun = function(a,b) return a.template_name < b.template_name end
	table.sort(bundles, sort_fun) -- sort license names so they always show up in the same order

	-- get attribution 
	-- If the license has author name string (P2093) and title (P1476), use that to construct the attribution 
	local author_str  = getQual(entity, P['copyright_license'], P['author_name_str']) 
	author_str = author_str or sdc_author(entity, args.lang, false) -- if no author than use the author shown in {{information}} template
	local title_str   = getQual(entity, P['copyright_license'], P['title'])
	local attribution = args.attribution or getProperty(entity, P['attribution_text']) or 
						get_default_attribution(bundles, author_str, title_str)
	
	-- Assemble template list, one template per line
	local msg
	bundles, msg = aggregate_multi_license_templates(bundles)
	local tag_list = {}
	if getProperty(entity, P['file_source']) == Q['own_work'] then 
		-- if source of file (P7482) -> original creation by uploader (Q66458942) then wrap the licenses in {{Self}}
		tag_list = form_self_template(bundles, author_str, attribution)
	else -- return all licenses as a list
		for _, bundle in ipairs(bundles) do -- add individual license to a list
			local license_tag = '{{' .. bundle['template_name'] .. '}}'
			if bundle['copyright_status'] == Q['copyrighted'] then
				license_tag = add_to_license_tag(license_tag, author_str, attribution)
			end
			table.insert(tag_list, license_tag)
		end
	end
	--table.insert(tag_list, '[' .. msg .. ']')
	
	-- find and replace some tags
	for itag, tag in ipairs(tag_list) do
		for _, pair in ipairs(find_and_replace) do
			if tag == pair[1] then
				tag_list[itag] = pair[2] --string.gsub( tag, pair[1], pair[2], 1 )
			end
		end
	end
	return tag_list
end

-- ===========================================================================
local function get_public_domain_tags(entity, args)
	--(not implemented yet)
	return {}
end

-- ==================================================
-- === External functions ===========================
-- ==================================================
local p = {}

-- ===========================================================================
-- === Version of the function to be called from other LUA codes
-- ===========================================================================

function p.copyright_tags(entity, args)
	local functions = {        -- list of functions for gathering copyright tags
		get_free_licenses,     -- look for free licenses
		get_public_domain_tags -- look for Public Domain tags
	}

	local tag_list ={}
	for _, get_tags in ipairs(functions) do
		local tags = get_tags(entity, args)
		if type(tags) == 'string' then
			return tags -- return error message
		else -- add to the list of tags
			for _, tag in ipairs(tags) do
				table.insert(tag_list, tag)
			end
		end
	end
	return tag_list
end

-- ===========================================================================
-- === Version of the functions to be called from template namespace
-- ===========================================================================

function p.SDC_license(frame)
	local args = core.getArgs(frame)
	
	-- get entity
	local page = mw.title.getCurrentTitle()
	local item_id, entity = args.id, nil
	if (item_id==nil) and (page.namespace == 6) then 
		entity = mw.wikibase.getEntity()
	elseif item_id and type(item_id)=='string' and item_id:match( '^[QqMm]%d+$' ) then
		entity = mw.wikibase.getEntity(item_id)
	elseif item_id and type(item_id)~='string' and item_id.id then
		entity = item_id -- entities can be passed from outside
	else 
		return ''
	end
	
	local tag_list = p.copyright_tags(entity, args)
	if type(tag_list) == 'string' then
		return tag_list -- return error message
	end
	local tags = table.concat(tag_list, '\n')
	if not args.debug then -- in debug mode do not expand templates
		tags = frame:preprocess(tags)
	end
	return tags
end

return p