Open main menu

Difference between revisions of "Module:Commons link"

m (1 revision imported)
(fix tracking logic, allow tracking on getCategory())
Line 15: Line 15:
 
local function _validProp(prop)
 
local function _validProp(prop)
 
return prop and mw.ustring.find(prop,"^[Pp]%d+$")
 
return prop and mw.ustring.find(prop,"^[Pp]%d+$")
 +
end
 +
 +
function _lcfirst(doit,s)
 +
if doit then
 +
return mw.ustring.lower(mw.ustring.sub(s,1,1))..mw.ustring.sub(s,2)
 +
end
 +
return s
 +
end
 +
 +
local function warning(msg)
 +
  local frame = mw.getCurrentFrame():getParent()
 +
  local html = ""
 +
  if frame:preprocess( "{{REVISIONID}}" ) == "" then
 +
  html = '<div style="color:red"><strong>Warning:</strong> '..msg
 +
  html = html..' <small>(this message is shown only in preview)</small></div>'
 +
  end
 +
  return html
 
end
 
end
  
Line 20: Line 37:
 
-- Arguments:
 
-- Arguments:
 
--  qid = testing only: get title of alternative page with QID=qid
 
--  qid = testing only: get title of alternative page with QID=qid
 +
--  nsQid = whether to return the ns of the qid page or current
 
-- Returns:
 
-- Returns:
--  title, namespace (number), qid of current page (or test page)
+
--  title, namespace (string), qid of current page (or test page)
local function _getTitleQID(qid)
+
local function _getTitleQID(qid,nsQid)
 
local titleObject = mw.title.getCurrentTitle()
 
local titleObject = mw.title.getCurrentTitle()
 
-- look up qid for current page (if not testing)
 
-- look up qid for current page (if not testing)
 +
local nsText = mw.ustring.gsub(titleObject.nsText,"_"," ")
 
if not _validQID(qid) then
 
if not _validQID(qid) then
 
qid = mw.wikibase.getEntityIdForCurrentPage()
 
qid = mw.wikibase.getEntityIdForCurrentPage()
return titleObject.text, titleObject.namespace, qid
+
return titleObject.text, nsText, qid
 
end
 
end
 
-- testing-only path: given a qid, determine title
 
-- testing-only path: given a qid, determine title
Line 35: Line 54:
 
-- strip any namespace from sitelink
 
-- strip any namespace from sitelink
 
local firstColon = mw.ustring.find(title,':',1,true)
 
local firstColon = mw.ustring.find(title,':',1,true)
 +
local qidNsText = ""
 
if firstColon then
 
if firstColon then
 +
qidNsText = mw.ustring.sub(title,1,firstColon-1)
 
title = mw.ustring.sub(title,firstColon+1)
 
title = mw.ustring.sub(title,firstColon+1)
 
end
 
end
return title, titleObject.namespace, qid
+
if nsQid then
 +
return title, qidNsText, qid
 +
end
 +
return title, nsText, qid
 
end
 
end
  
Line 157: Line 181:
 
end
 
end
  
 +
-- Does the article have a corresponding Commons gallery?
 +
-- Arguments:
 +
--  qid = QID to lookup in wikidata (for testing only)
 +
-- Returns:
 +
--  filename at Commons if so, nil if not
 +
function p._hasGallery(qid)
 +
local wp_title, wp_ns
 +
wp_title, wp_ns, qid = _getTitleQID(qid)
 +
local galleryLink, consistent = _lookupGallery(qid,true)
 +
if galleryLink and consistent then
 +
return galleryLink
 +
end
 +
return nil
 +
end
 +
 +
-- Does the article have a corresponding Commons category?
 +
-- Arguments:
 +
--  qid = QID to lookup in wikidata (for testing only)
 +
--  prefix = whether to add "Category:" to return string (default true)
 +
-- Returns:
 +
--  filename at Commons if so, blank if not
 +
function p._hasCategory(qid,prefix)
 +
if prefix == nil then
 +
prefix = true
 +
end
 +
local wp_title, wp_ns
 +
wp_title, wp_ns, qid = _getTitleQID(qid)
 +
local categoryLink, consistent = _lookupCategory(qid,true)
 +
if categoryLink and consistent then
 +
if prefix then
 +
categoryLink = "Category:"..categoryLink
 +
end
 +
return categoryLink
 +
end
 +
return nil
 +
end
  
 
-- Create Commons link corresponding to current article
 
-- Create Commons link corresponding to current article
Line 164: Line 224:
 
--  linktext = text to display in link
 
--  linktext = text to display in link
 
--  search = string to search for
 
--  search = string to search for
 +
--  lcfirst = lower case the first letter in linktext
 
--  qid = QID to lookup in wikidata (for testing only)
 
--  qid = QID to lookup in wikidata (for testing only)
 
-- Returns:
 
-- Returns:
 
--  formatted wikilink to Commons in specified namespace
 
--  formatted wikilink to Commons in specified namespace
function p._getCommons(namespace,default,linktext,search,qid)
+
function p._getCommons(namespace,default,linktext,search,lcfirst,qid)
 
local nsColon
 
local nsColon
 
if not namespace or namespace == "" then
 
if not namespace or namespace == "" then
Line 175: Line 236:
 
end
 
end
 
if default then
 
if default then
return "[[Commons:"..nsColon..default.."|"..(linktext or default).."]]"
+
return "[[Commons:"..nsColon..default.."|".._lcfirst(lcfirst,linktext or default).."]]"
 
end
 
end
 
if search then
 
if search then
return "[[Commons:Special:Search/"..nsColon..search.."|"..(linktext or search).."]]"
+
return "[[Commons:Special:Search/"..nsColon..search.."|".._lcfirst(lcfirst,linktext or search).."]]"
 
end
 
end
 
local wp_title, wp_ns
 
local wp_title, wp_ns
 
wp_title, wp_ns, qid = _getTitleQID(qid)
 
wp_title, wp_ns, qid = _getTitleQID(qid)
 
-- construct default result (which searches for title)
 
-- construct default result (which searches for title)
local searchResult = "[[Commons:Special:Search/"..nsColon..wp_title.."|"..(linktext or wp_title).."]]"
+
local searchResult = "[[Commons:Special:Search/"..nsColon..wp_title.."|".._lcfirst(lcfirst,linktext or wp_title).."]]"
 
local commonsLink = nil
 
local commonsLink = nil
 
local consistent = true
 
local consistent = true
Line 193: Line 254:
 
-- use wikidata if consistent
 
-- use wikidata if consistent
 
if commonsLink and consistent then
 
if commonsLink and consistent then
return "[[Commons:"..nsColon..commonsLink.."|"..(linktext or commonsLink).."]]"
+
return "[[Commons:"..nsColon..commonsLink.."|".._lcfirst(lcfirst,linktext or commonsLink).."]]"
 
end
 
end
 
-- if not consistent, fall back to search and add to tracking cat
 
-- if not consistent, fall back to search and add to tracking cat
if not consistent and wp_ns == 0 then
+
if not consistent and wp_ns == "" then
 
local friendlyNS
 
local friendlyNS
 
if nsColon == "" then
 
if nsColon == "" then
Line 233: Line 294:
 
return "[[Commons:"..galleryLink.."|"..(linktext or galleryLink).."]]"
 
return "[[Commons:"..galleryLink.."|"..(linktext or galleryLink).."]]"
 
end
 
end
if not consistent and wp_ns == 0 then
+
if not consistent and wp_ns == "" then
 
trackingCats = "[[Category:Inconsistent wikidata for Commons gallery]]"
 
trackingCats = "[[Category:Inconsistent wikidata for Commons gallery]]"
 
end
 
end
Line 242: Line 303:
 
return "[[Commons:Category:"..categoryLink.."|"..(linktext or categoryLink).."]]"..trackingCats
 
return "[[Commons:Category:"..categoryLink.."|"..(linktext or categoryLink).."]]"..trackingCats
 
end
 
end
if not consistent and wp_ns == 0 then
+
if not consistent and wp_ns == "" then
 
trackingCats = trackingCats.."[[Category:Inconsistent wikidata for Commons category]]"
 
trackingCats = trackingCats.."[[Category:Inconsistent wikidata for Commons category]]"
 
end
 
end
Line 248: Line 309:
 
end
 
end
  
-- Does the article have a corresponding Commons gallery or category?
+
-- Make a string bold, italic, or both
 
-- Arguments:
 
-- Arguments:
--  qid = QID to lookup in wikidata (for testing only)
+
--  s = string to format
 +
--  bold = make it bold
 +
--  italic = make it italic
 
-- Returns:
 
-- Returns:
--  "yes" if so, blank if not
+
--  string modified with html tags
function p._hasGalleryOrCategory(qid)
+
local function _formatResult(s,bold,italic)
 +
local resultVal = ""
 +
if bold then resultVal = "<b>" end
 +
if italic then resultVal = resultVal.."<i>" end
 +
resultVal = resultVal..s
 +
if italic then resultVal = resultVal.."</i>" end
 +
if bold then resultVal = resultVal.."</b>" end
 +
return resultVal
 +
end
 +
 
 +
-- Return link(s) Commons gallery, or category, or both from wikidata
 +
-- Arguments:
 +
--  defaultGallery = default gallery link to use, instead of wikidata
 +
--  defaultCategory = default category link to use, instead of wikidata
 +
--  categoryText = if both gallery and category, text to use in category link ("category" by default)
 +
--  bold = whether to make first link bold
 +
--  italic = whether to make first link italic
 +
--  qid = qid of page to lookup in wikidata (testing only)
 +
function p._getGalleryAndCategory(defaultGallery,defaultCategory,linkText,categoryText,bold,italic,oneSearch,qid)
 
local wp_title, wp_ns
 
local wp_title, wp_ns
 
wp_title, wp_ns, qid = _getTitleQID(qid)
 
wp_title, wp_ns, qid = _getTitleQID(qid)
local galleryLink, consistent, commonsSitelink = _lookupGallery(qid,true)
+
categoryText = categoryText or "category"
if galleryLink and consistent then
+
-- construct default result (which searches for title)
return "yes"
+
local searchResult = _formatResult("[[Commons:Special:Search/"..wp_title.."|"..(linkText or wp_title).."]]",bold,italic)
 +
if not oneSearch then
 +
searchResult = searchResult.." ([[Commons:Special:Search/Category:"..wp_title.."|"..categoryText.."]])"
 +
end
 +
local trackingCats = ""
 +
local galleryLink, galleryConsistent
 +
local commonsSitelink = nil
 +
if defaultGallery then
 +
galleryLink = defaultGallery
 +
galleryConsistent = true
 +
else
 +
galleryLink, galleryConsistent, commonsSitelink = _lookupGallery(qid,true)
 +
end
 +
local galleryGood = galleryLink and galleryConsistent
 +
if not galleryConsistent and wp_ns == "" then
 +
trackingCats = "[[Category:Inconsistent wikidata for Commons gallery]]"
 +
end
 +
local categoryLink, categoryConsistent
 +
if defaultCategory then
 +
categoryLink = defaultCategory
 +
categoryConsistent = true
 +
else
 +
categoryLink, categoryConsistent = _lookupCategory(qid,defaultGallery,commonsSitelink)
 +
end
 +
local categoryGood = categoryLink and categoryConsistent
 +
if not categoryConsistent and wp_ns == "" then
 +
trackingCats = trackingCats.."[[Category:Inconsistent wikidata for Commons category]]"
 +
end
 +
local firstLink
 +
if galleryGood then
 +
firstLink = galleryLink
 +
linkText = linkText or galleryLink
 +
elseif categoryGood then
 +
firstLink = "Category:"..categoryLink
 +
linkText = linkText or categoryLink
 +
else
 +
return searchResult..trackingCats
 
end
 
end
local categoryLink
+
local resultVal = _formatResult("[[Commons:"..firstLink.."|"..linkText.."]]",bold,italic)
categoryLink, consistent = _lookupCategory(qid,false,commonsSitelink)
+
if galleryGood and categoryGood then
if categoryLink and consistent then
+
resultVal = resultVal.." ([[Commons:Category:"..categoryLink.."|"..categoryText.."]])"
return "yes"
 
 
end
 
end
return ""
+
return resultVal..trackingCats
 +
end
 +
 
 +
-- Compare two titles with their namespaces stripped
 +
local function titleMatch(s1,s2)
 +
s1 = s1 or ""
 +
s2 = s2 or ""
 +
    s1 = mw.ustring.gsub(s1,"^[^:]+:","")
 +
    s2 = mw.ustring.gsub(s2,"^[^:]+:","")
 +
    return s1 == s2
 
end
 
end
  
 +
-- Figure out tracking categories and editor warnings
 +
-- Arguments:
 +
--  default = Commons link argument passed to template
 +
--  fetchGallery = whether to fetch a gallery from Wikidata
 +
--  fetchCategory = whether to fetch a category from Wikidata
 +
--  qid = force a qid for testing
 +
-- Returns:
 +
--  tracking category and possible user warning
 +
--
 +
-- Note: the logic for the tracking is quite different than the logic
 +
-- for generating Commons links (above). Thus, it is separated into another
 +
-- function for code clarity and maintainability. This should not seriously
 +
-- affect performance: server time is dominated by fetching wikidata entities,
 +
-- and those entities should be cached and shared between the Commons generating
 +
-- code and this tracking code.
 +
function p._tracking(default, fetchGallery, fetchCategory, qid)
 +
local title, wp_ns, wp_qid = _getTitleQID(qid,true)
 +
if wp_ns ~= "" then
 +
title = wp_ns..":"..title
 +
end
 +
-- only track if test or namespace=article or namespace=category
 +
if not (qid or wp_ns == "" or wp_ns == "Category") then
 +
return ""
 +
end
 +
-- construct warning message
 +
local msg = "Commons link does not match Wikidata"
 +
msg = msg.."– [[Template:Commons_category#Resolving_discrepancies|please check]]"
 +
local prefix = "[[Category:Commons "
 +
if not fetchGallery and fetchCategory then
 +
prefix = prefix.."category "
 +
end
 +
prefix = prefix.."link "
 +
-- determine title and namespace of wikidata and wp article
 +
local wikidata = nil
 +
-- Tracking code works for all 4 cases of states of fetchGallery/Category
 +
-- fetchGallery takes precedence
 +
if fetchGallery then
 +
wikidata = p._hasGallery(qid)
 +
end
 +
if wikidata == nil and fetchCategory then
 +
wikidata = p._hasCategory(qid,true)
 +
end
 +
    local wp_cat = (wp_ns == "Category")
 +
 +
if default then
 +
if default == wikidata then
 +
return prefix.."is on Wikidata]]"
 +
end
 +
if titleMatch(default,title) then
 +
return prefix.."is defined as the pagename]]"..warning(msg)
 +
end
 +
return prefix.."is locally defined]]"..warning(msg)
 +
end
 +
if wikidata then
 +
return prefix.."from Wikidata]]"
 +
end
 +
return prefix.."is the pagename]]"
 +
end
  
 
-- Testing-only entry point for _getTitleQID
 
-- Testing-only entry point for _getTitleQID
 
function p.getTitleQID(frame)
 
function p.getTitleQID(frame)
 
local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
 
local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
local text, ns, qid = _getTitleQID(args[1])
+
local text, ns, qid = _getTitleQID(args[1],args[2])
 
return text..","..ns..","..(qid or "nil")
 
return text..","..ns..","..(qid or "nil")
 
end
 
end
Line 286: Line 469:
 
function p.getGallery(frame)
 
function p.getGallery(frame)
 
local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
 
local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
return p._getCommons("",args[1],args.linktext,args.search,args.qid)
+
return p._getCommons("",args[1],args.linktext,args.search,args.lcfirst,args.qid)
 
end
 
end
  
Line 292: Line 475:
 
function p.getCategory(frame)
 
function p.getCategory(frame)
 
local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
 
local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
return p._getCommons("Category",args[1],args.linktext,args.search,args.qid)
+
local retval = p._getCommons("Category",args[1],args.linktext,args.search,args.lcfirst,args.qid)
 +
if args.tracking then
 +
local default = nil
 +
if args[1] then
 +
default = "Category:"..args[1]
 +
end
 +
retval = retval..p._tracking(default,false,true,args.qid)
 +
end
 +
return retval
 
end
 
end
  
 
function p.getGalleryOrCategory(frame)
 
function p.getGalleryOrCategory(frame)
 
local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
 
local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
return p._getGalleryOrCategory(args[1],args.linktext,args.search,args.qid)
+
local retval = p._getGalleryOrCategory(args[1],args.linktext,args.search,args.qid)
 +
if args.tracking then
 +
retval = retval..p._tracking(args[1],true,true,args.qid)
 +
end
 +
return retval
 +
end
 +
 
 +
function p.hasGallery(frame)
 +
local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
 +
return p._hasGallery(args.qid) or ""
 +
end
 +
 
 +
function p.hasCategory(frame)
 +
local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
 +
return p._hasCategory(args.qid) or ""
 
end
 
end
  
 
function p.hasGalleryOrCategory(frame)
 
function p.hasGalleryOrCategory(frame)
 
local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
 
local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
return p._hasGalleryOrCategory(args.qid)
+
return p._hasGallery(args.qid) or p._hasCategory(args.qid) or ""
 
end
 
end
  
 +
function p.getGalleryAndCategory(frame)
 +
local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
 +
return p._getGalleryAndCategory(args[1],args[2],args.linktext,args.categoryText,
 +
args.bold,args.italic,args.oneSearch,args.qid)
 +
end
 +
 +
function p.tracking(frame)
 +
local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
 +
return p._tracking(args[1],args.fetchGallery,args.fetchCategory,args.qid)
 +
end
  
 
return p
 
return p

Revision as of 11:37, 29 September 2020

Lua error in package.lua at line 80: module 'strict' not found.

Usage

{{#invoke:Commons link|getGallery}}

Use wikidata to find Commons gallery corresponding to this article. If unable to find gallery in wikidata, default to searching for PAGENAME in Commons.

{{#invoke:Commons link|getGallery|title|linktext=text}}

Link to Commons gallery at title. Optionally, use text as displayed link text.

{{#invoke:Commons link|getGallery|search=string|linktext=text}}

Link to Commons search for string. Optionally, use text as displayed link text.

{{#invoke:Commons link|getCategory|fallback=string|linktext=text}}

Use wikidata first, then if failure, use Commons search for string. Optionally, use text as displayed link text.

{{#invoke:Commons link|getCategory}}

Use wikidata to find Commons category corresponding to this article. If unable to find category in wikidata, default to searching for Category:PAGENAME in Commons.

{{#invoke:Commons link|getCategory|title|linktext=text}}

Link to Commons category at Category:title. Optionally, use text as displayed link text.

{{#invoke:Commons link|getCategory|search=string|linktext=text}}

Link to Commons search for Category:string. Optionally, use text as displayed link text.

{{#invoke:Commons link|getCategory|fallback=string|linktext=text}}

Use wikidata first, then if failure, use Commons search for Category:string. Optionally, use text as displayed link text.

{{#invoke:Commons link|getGalleryOrCategory}}

Use wikidata to find "best" single Commons link: try gallery first, fall back to category. Other arguments as above.

{{#invoke:Commons link|getGalleryAndCategory}}

Lua to implement {{commons and category}}: return Commons gallery, Commons category, or both (if both found)

{{#invoke:Commons link|getGalleryAndCategory|GalleryName|CategoryName}}

Either GalleryName or CategoryName or both can be supplied, will override wikidata search

{{#invoke:Commons link|bold=1|italic=1}}

Format of first link can be specified (bold or italic or both)

{{#invoke:Commons link|linktext=link|categoryText=category}}

Text in the first link, and the second (category) link can be overridden, also.

-- Module to find commons galleries and categories based on wikidata entries
local getArgs = require('Module:Arguments').getArgs
local p = {}

-- Check if string is a valid QID
-- Argument: QID to check
-- Returns: valid (bool)
local function _validQID(qid)
	return qid and mw.ustring.find(qid,"^[Qq]%d+$")
end

-- Check if string is a valid wikidata property string
-- Argument: property string to check
-- Returns: valid (bool)
local function _validProp(prop)
	return prop and mw.ustring.find(prop,"^[Pp]%d+$")
end

function _lcfirst(doit,s)
	if doit then
		return mw.ustring.lower(mw.ustring.sub(s,1,1))..mw.ustring.sub(s,2)
	end
	return s
end

local function warning(msg)
  local frame = mw.getCurrentFrame():getParent()
  local html = ""
  if frame:preprocess( "{{REVISIONID}}" ) == "" then 
  	html = '<div style="color:red"><strong>Warning:</strong> '..msg
  	html = html..' <small>(this message is shown only in preview)</small></div>'
  end
  return html
end

-- Get title, namespace, and QID for current page
-- Arguments:
--   qid = testing only: get title of alternative page with QID=qid
--   nsQid = whether to return the ns of the qid page or current
-- Returns:
--   title, namespace (string), qid of current page (or test page)
local function _getTitleQID(qid,nsQid)
	local titleObject = mw.title.getCurrentTitle()
	-- look up qid for current page (if not testing)
	local nsText = mw.ustring.gsub(titleObject.nsText,"_"," ")
	if not _validQID(qid) then
		qid = mw.wikibase.getEntityIdForCurrentPage()
		return titleObject.text, nsText, qid
	end
	-- testing-only path: given a qid, determine title
	-- always use namespace from current page (to suppress tracking cat)
	qid = qid:upper()
	local title = mw.wikibase.getSitelink(qid) or ""
	-- strip any namespace from sitelink
	local firstColon = mw.ustring.find(title,':',1,true)
	local qidNsText = ""
	if firstColon then
		qidNsText = mw.ustring.sub(title,1,firstColon-1)
		title = mw.ustring.sub(title,firstColon+1)
	end
	if nsQid then
		return title, qidNsText, qid
	end
	return title, nsText, qid
end

-- Lookup Commons gallery in Wikidata
-- Arguments:
--   qid = QID of current article
--   fetch = whether to lookup Commons sitelink (bool)
--   commonsSitelink = default value for Commons sitelink
-- Returns:
--   categoryLink = name of Commons category, nil if nothing is found
--   consistent = multiple wikidata fields are examined: are they consistent?
--   commonsSitelink = commons sitelink for current article
local function _lookupGallery(qid,fetch,commonsSitelink)
	if not _validQID(qid) then
		return nil, true, nil
	end
	qid = qid:upper()
	local galleryLink = nil
	local consistent = true
	-- look up commons sitelink for article, use if not category
	if fetch then
		commonsSitelink = mw.wikibase.getSitelink(qid,"commonswiki") or commonsSitelink
	end
	if commonsSitelink and mw.ustring.sub(commonsSitelink,1,9) ~= "Category:" then
		galleryLink = commonsSitelink
	end
	-- P935 is the "commons gallery" property for this article
	local P935 = mw.wikibase.getBestStatements(qid, "P935")[1]
	if P935 and P935.mainsnak.datavalue then
		local gallery = P935.mainsnak.datavalue.value
		if galleryLink and galleryLink ~= gallery then
			consistent = false
		else
			galleryLink = gallery
		end
	end
	return galleryLink, consistent, commonsSitelink
end

-- Find fallback category by looking up Commons sitelink of different page
-- Arguments:
--    qid = QID for current article
--    property = property that refers to other article whose sitelink to return
-- Returns: either category-stripped name of article, or nil
local function _lookupFallback(qid,property)
	if not _validQID(qid) or not _validProp(property) then
		return nil
	end
	qid = qid:upper()
	property = property:upper()
	-- If property exists on current article, get value (other article qid)
	local value = mw.wikibase.getBestStatements(qid, property)[1]
	if value and value.mainsnak.datavalue and value.mainsnak.datavalue.value.id then
		-- Look up Commons sitelink of other article
		local sitelink = mw.wikibase.getSitelink(value.mainsnak.datavalue.value.id,"commonswiki")
		-- Check to see if it starts with "Category:". If so, strip it and return
		if sitelink and mw.ustring.sub(sitelink,1,9) == "Category:" then
			return mw.ustring.sub(sitelink,10)
		end
	end
	return nil
end

-- Find Commons category by looking in wikidata
-- Arguments:
--   qid = QID of current article
--   fetch = whether to lookup Commons sitelink (bool)
--   commonsSitelink = default value for Commons sitelink
-- Returns:
--   categoryLink = name of Commons category, nil if nothing is found
--   consistent = multiple wikidata fields are examined: are they consistent?
--   commonsSitelink = commons sitelink for current article
local function _lookupCategory(qid, fetch, commonsSitelink)
	if not _validQID(qid) then
		return nil, true, nil
	end
	qid = qid:upper()
	local categoryLink = nil
	local consistent = true
	-- look up commons sitelink for article, use if starts with "Category:"
	if fetch then
		commonsSitelink = mw.wikibase.getSitelink(qid,"commonswiki") or commonsSitelink
	end
	if commonsSitelink and mw.ustring.sub(commonsSitelink,1,9) == "Category:" then
		categoryLink = mw.ustring.sub(commonsSitelink,10)
	end
	-- P373 is the "commons category" property for this article
	local P373 = mw.wikibase.getBestStatements(qid, "P373")[1]
	if P373 and P373.mainsnak.datavalue then
		P373 = P373.mainsnak.datavalue.value
		if categoryLink and categoryLink ~= P373 then
			consistent = false
			qid = nil  -- stop searching on inconsistent data
		else
			categoryLink = P373
		end
	end
	-- P910 is the "topic's main category". Look for commons sitelink there
	local fallback = _lookupFallback(qid,"P910")
	if fallback then
		if categoryLink and categoryLink ~= fallback then
			consistent = false
			qid = nil
		else
			categoryLink = fallback
		end
	end
	-- P1754 is the "list's main category". Look for commons sitelink there
	fallback = _lookupFallback(qid,"P1754")
	if fallback then
		if categoryLink and categoryLink ~= fallback then
			consistent = false
		else
			categoryLink = fallback
		end
	end
	return categoryLink, consistent, commonsSitelink
end

-- Does the article have a corresponding Commons gallery?
-- Arguments:
--   qid = QID to lookup in wikidata (for testing only)
-- Returns:
--   filename at Commons if so, nil if not
function p._hasGallery(qid)
	local wp_title, wp_ns
	wp_title, wp_ns, qid = _getTitleQID(qid)
	local galleryLink, consistent = _lookupGallery(qid,true)
	if galleryLink and consistent then
		return galleryLink
	end
	return nil
end

-- Does the article have a corresponding Commons category?
-- Arguments:
--   qid = QID to lookup in wikidata (for testing only)
--   prefix = whether to add "Category:" to return string (default true)
-- Returns:
--   filename at Commons if so, blank if not
function p._hasCategory(qid,prefix)
	if prefix == nil then
		prefix = true
	end
	local wp_title, wp_ns
	wp_title, wp_ns, qid = _getTitleQID(qid)
	local categoryLink, consistent = _lookupCategory(qid,true)
	if categoryLink and consistent then
		if prefix then
			categoryLink = "Category:"..categoryLink
		end
		return categoryLink
	end
	return nil
end

-- Create Commons link corresponding to current article
-- Arguments:
--   namespace = namespace in Commons ("" for galleries)
--   default = use as Commons link, don't access wikidata
--   linktext = text to display in link
--   search = string to search for
--   lcfirst = lower case the first letter in linktext
--   qid = QID to lookup in wikidata (for testing only)
-- Returns:
--   formatted wikilink to Commons in specified namespace
function p._getCommons(namespace,default,linktext,search,lcfirst,qid)
	local nsColon
	if not namespace or namespace == "" then
		nsColon = ""
	else
		nsColon = namespace..":"
	end
	if default then
		return "[[Commons:"..nsColon..default.."|".._lcfirst(lcfirst,linktext or default).."]]"
	end
	if search then
		return "[[Commons:Special:Search/"..nsColon..search.."|".._lcfirst(lcfirst,linktext or search).."]]"
	end
	local wp_title, wp_ns
	wp_title, wp_ns, qid = _getTitleQID(qid)
	-- construct default result (which searches for title)
	local searchResult = "[[Commons:Special:Search/"..nsColon..wp_title.."|".._lcfirst(lcfirst,linktext or wp_title).."]]"
	local commonsLink = nil
	local consistent = true
	if nsColon == "" then
		commonsLink, consistent = _lookupGallery(qid,true)
	elseif namespace:lower() == "category" then
		commonsLink, consistent = _lookupCategory(qid,true)
	end
	-- use wikidata if consistent
	if commonsLink and consistent then
		return "[[Commons:"..nsColon..commonsLink.."|".._lcfirst(lcfirst,linktext or commonsLink).."]]"
	end
	-- if not consistent, fall back to search and add to tracking cat
	if not consistent and wp_ns == "" then
		local friendlyNS
		if nsColon == "" then
			friendlyNS = "gallery"
		else
			friendlyNS = namespace:lower()
		end
		searchResult = searchResult.."[[Category:Inconsistent wikidata for Commons "..friendlyNS.."]]"
	end
	return searchResult
end

-- Returns "best" Commons link: first look for gallery, then try category
-- Arguments:
--   default = use as Commons link, don't access wikidata
--   linktext = text to display in link
--   search = string to search for
--   qid = QID to lookup in wikidata (for testing only)
-- Returns:
--   formatted wikilink to Commons "best" landing page
function p._getGalleryOrCategory(default,linktext,search,qid)
	if default then
		return "[[Commons:"..default.."|"..(linktext or default).."]]"
	end
	if search then
		return "[[Commons:Special:Search/"..search.."|"..(linktext or search).."]]"
	end
	local wp_title, wp_ns
	wp_title, wp_ns, qid = _getTitleQID(qid)
	-- construct default result (which searches for title)
	local searchResult = "[[Commons:Special:Search/"..wp_title.."|"..(linktext or wp_title).."]]"
	local trackingCats = ""
	local galleryLink, consistent, commonsSitelink = _lookupGallery(qid,true)
	-- use wikidata if either sitelink or P935 exist, and they both agree
	if galleryLink and consistent then
		return "[[Commons:"..galleryLink.."|"..(linktext or galleryLink).."]]"
	end
	if not consistent and wp_ns == "" then
		trackingCats = "[[Category:Inconsistent wikidata for Commons gallery]]"
	end
	-- if gallery is not good, fall back looking for category
	local categoryLink
	categoryLink, consistent = _lookupCategory(qid,false,commonsSitelink)
	if categoryLink and consistent then
		return "[[Commons:Category:"..categoryLink.."|"..(linktext or categoryLink).."]]"..trackingCats
	end
	if not consistent and wp_ns == "" then
		trackingCats = trackingCats.."[[Category:Inconsistent wikidata for Commons category]]"
	end
	return searchResult..trackingCats
end

-- Make a string bold, italic, or both
-- Arguments:
--   s = string to format
--   bold = make it bold
--   italic = make it italic
-- Returns:
--   string modified with html tags
local function _formatResult(s,bold,italic)
	local resultVal = ""
	if bold then resultVal = "<b>" end
	if italic then resultVal = resultVal.."<i>" end
	resultVal = resultVal..s
	if italic then resultVal = resultVal.."</i>" end
	if bold then resultVal = resultVal.."</b>" end
	return resultVal
end

-- Return link(s) Commons gallery, or category, or both from wikidata
-- Arguments:
--   defaultGallery = default gallery link to use, instead of wikidata
--   defaultCategory = default category link to use, instead of wikidata
--   categoryText = if both gallery and category, text to use in category link ("category" by default)
--   bold = whether to make first link bold
--   italic = whether to make first link italic
--   qid = qid of page to lookup in wikidata (testing only)
function p._getGalleryAndCategory(defaultGallery,defaultCategory,linkText,categoryText,bold,italic,oneSearch,qid)
	local wp_title, wp_ns
	wp_title, wp_ns, qid = _getTitleQID(qid)
	categoryText = categoryText or "category"
	-- construct default result (which searches for title)
	local searchResult = _formatResult("[[Commons:Special:Search/"..wp_title.."|"..(linkText or wp_title).."]]",bold,italic)
	if not oneSearch then
		searchResult = searchResult.." ([[Commons:Special:Search/Category:"..wp_title.."|"..categoryText.."]])"
	end
	local trackingCats = ""
	local galleryLink, galleryConsistent
	local commonsSitelink = nil
	if defaultGallery then
		galleryLink = defaultGallery
		galleryConsistent = true
	else
		galleryLink, galleryConsistent, commonsSitelink = _lookupGallery(qid,true)
	end
	local galleryGood = galleryLink and galleryConsistent
	if not galleryConsistent and wp_ns == "" then
		trackingCats = "[[Category:Inconsistent wikidata for Commons gallery]]"
	end
	local categoryLink, categoryConsistent
	if defaultCategory then
		categoryLink = defaultCategory
		categoryConsistent = true
	else
		categoryLink, categoryConsistent = _lookupCategory(qid,defaultGallery,commonsSitelink)
	end
	local categoryGood = categoryLink and categoryConsistent
	if not categoryConsistent and wp_ns == "" then
		trackingCats = trackingCats.."[[Category:Inconsistent wikidata for Commons category]]"
	end
	local firstLink
	if galleryGood then
		firstLink = galleryLink
		linkText = linkText or galleryLink
	elseif categoryGood then
		firstLink = "Category:"..categoryLink
		linkText = linkText or categoryLink
	else
		return searchResult..trackingCats
	end
	local resultVal = _formatResult("[[Commons:"..firstLink.."|"..linkText.."]]",bold,italic)
	if galleryGood and categoryGood then
		resultVal = resultVal.." ([[Commons:Category:"..categoryLink.."|"..categoryText.."]])"
	end
	return resultVal..trackingCats
end

-- Compare two titles with their namespaces stripped
local function titleMatch(s1,s2)
	s1 = s1 or ""
	s2 = s2 or ""
    s1 = mw.ustring.gsub(s1,"^[^:]+:","")
    s2 = mw.ustring.gsub(s2,"^[^:]+:","")
    return s1 == s2
end

-- Figure out tracking categories and editor warnings
-- Arguments:
--   default = Commons link argument passed to template
--   fetchGallery = whether to fetch a gallery from Wikidata
--   fetchCategory = whether to fetch a category from Wikidata
--   qid = force a qid for testing
-- Returns:
--   tracking category and possible user warning
--
-- Note: the logic for the tracking is quite different than the logic
-- for generating Commons links (above). Thus, it is separated into another
-- function for code clarity and maintainability. This should not seriously 
-- affect performance: server time is dominated by fetching wikidata entities,
-- and those entities should be cached and shared between the Commons generating
-- code and this tracking code.
function p._tracking(default, fetchGallery, fetchCategory, qid)
	local title, wp_ns, wp_qid = _getTitleQID(qid,true)
	if wp_ns ~= "" then
		title = wp_ns..":"..title
	end
	-- only track if test or namespace=article or namespace=category
	if not (qid or wp_ns == "" or wp_ns == "Category") then
		return ""
	end
	-- construct warning message
	local msg = "Commons link does not match Wikidata"
	msg = msg.."– [[Template:Commons_category#Resolving_discrepancies|please check]]"
	local prefix = "[[Category:Commons "
	if not fetchGallery and fetchCategory then
		prefix = prefix.."category "
	end
	prefix = prefix.."link "
	-- determine title and namespace of wikidata and wp article
	local wikidata = nil
	-- Tracking code works for all 4 cases of states of fetchGallery/Category
	-- fetchGallery takes precedence
	if fetchGallery then
		wikidata = p._hasGallery(qid)
	end
	if wikidata == nil and fetchCategory then
		wikidata = p._hasCategory(qid,true)
	end
    local wp_cat = (wp_ns == "Category")

	if default then
		if default == wikidata then
			return prefix.."is on Wikidata]]"
		end
		if titleMatch(default,title) then
			return prefix.."is defined as the pagename]]"..warning(msg)
		end
		return prefix.."is locally defined]]"..warning(msg)
	end
	if wikidata then
		return prefix.."from Wikidata]]"
	end
	return prefix.."is the pagename]]"
end

-- Testing-only entry point for _getTitleQID
function p.getTitleQID(frame)
	local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
	local text, ns, qid = _getTitleQID(args[1],args[2])
	return text..","..ns..","..(qid or "nil")
end

-- Testing-only entry point for _lookupFallback
function p.lookupFallback(frame)
	local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
	local fallback = _lookupFallback(args[1],args[2])
	return fallback or "nil"
end

-- Find the Commons gallery page associated with article
function p.getGallery(frame)
	local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
	return p._getCommons("",args[1],args.linktext,args.search,args.lcfirst,args.qid)
end

-- Find the Commons category page associated with article
function p.getCategory(frame)
	local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
	local retval = p._getCommons("Category",args[1],args.linktext,args.search,args.lcfirst,args.qid)
	if args.tracking then
		local default = nil
		if args[1] then
			default = "Category:"..args[1]
		end
		retval = retval..p._tracking(default,false,true,args.qid)
	end
	return retval
end

function p.getGalleryOrCategory(frame)
	local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
	local retval = p._getGalleryOrCategory(args[1],args.linktext,args.search,args.qid)
	if args.tracking then
		retval = retval..p._tracking(args[1],true,true,args.qid)
	end
	return retval
end

function p.hasGallery(frame)
	local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
	return p._hasGallery(args.qid) or ""
end

function p.hasCategory(frame)
	local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
	return p._hasCategory(args.qid) or ""
end

function p.hasGalleryOrCategory(frame)
	local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
	return p._hasGallery(args.qid) or p._hasCategory(args.qid) or ""
end

function p.getGalleryAndCategory(frame)
	local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
	return p._getGalleryAndCategory(args[1],args[2],args.linktext,args.categoryText,
		args.bold,args.italic,args.oneSearch,args.qid)
end

function p.tracking(frame)
	local args = getArgs(frame,{frameOnly=true,parentOnly=false,parentFirst=false})
	return p._tracking(args[1],args.fetchGallery,args.fetchCategory,args.qid)
end

return p