require('strict')  -- 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  local function _lcfirst(s) 	return mw.ustring.lower(mw.ustring.sub(s,1,1))..mw.ustring.sub(s,2) end  -- Format displayed linktext -- Arguments: --   s = string to display --   formatting = formatting table: --    formatting.linktext = if defined, override s --    formatting.lcfirst = lower case the first letter in display --    formatting.bold = whether to bold the display --    formatting.italic = whether to italicize the display --    formatting.nowrap = set nowrapping -- Returns: --   formatted string local function _formatResult(s, formatting) 	local resultVal = formatting.linktext or s 	if formatting.lcfirst then 		resultVal = _lcfirst(resultVal) 	end     local style = "" 	if formatting.italic then style = "font-style:italic; " end 	if formatting.bold then style = style.."font-weight:bold; " end 	if formatting.nowrap then style = style.."white-space:nowrap; " end     if style ~= "" then     	resultVal = ''..resultVal..''     end 	return resultVal 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 = string.gsub(titleObject.nsText,"_"," ") -- phab:T369784 	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 	-- 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 			qid = nil 		else 			categoryLink = fallback 		end 	end     -- P373 is the "commons category" property for this article. This is     -- a low-quality field, so should only be used as a last resort.     if categoryLink == nil and _validQID(qid) then 	    local P373 = mw.wikibase.getBestStatements(qid, "P373")[1] 	    if P373 and P373.mainsnak.datavalue then 		    categoryLink = P373.mainsnak.datavalue.value 		    consistent = true  -- P373 is never used if anything else is available 		end 	end 	return categoryLink, consistent, commonsSitelink end  -- Does the article have a Commons gallery, and is it consistent? -- Arguments: --   qid = QID to lookup in wikidata (for testing only) -- Returns: --   filename at Commons, bool: is wikidata consistent for this article? function p._hasGalleryConsistent(qid) 	local wp_title, wp_ns 	wp_title, wp_ns, qid = _getTitleQID(qid) 	return _lookupGallery(qid,true) 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, false if not function p._hasGallery(qid) 	local galleryLink, consistent = p._hasGalleryConsistent(qid) 	return consistent and galleryLink end  -- Does the article have a Commons category? Is wikidata consistent for that? -- Arguments: --   qid = QID to lookup in wikidata (for testing only) --   prefix = whether to add "Category:" to return string (default true) -- Returns: --   filename at Commons, bool: consistent function p._hasCategoryConsistent(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 prefix then 		categoryLink = "Category:"..categoryLink 	end 	return categoryLink, consistent 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) 	local categoryLink, consistent = p._hasCategoryConsistent(qid,prefix) 	return consistent and categoryLink end  -- Create Commons link corresponding to current article -- Arguments: --   namespace = namespace in Commons ("" for galleries) --   default = use as Commons link, don't access wikidata --   search = string to search for --   fallback = string to search for if wikidata fails --   formatting = formatting parameters --   qid = QID to lookup in wikidata (for testing only) -- Returns: --   formatted wikilink to Commons in specified namespace function p._getCommons(namespace,default,search,fallback,formatting,qid) 	local nsColon 	if not namespace or namespace == "" then 		nsColon = "" 	else 		nsColon = namespace..":" 	end 	if default then 		return "".._formatResult(default,formatting).."" 	end 	if search then 		return "".._formatResult(search,formatting).."" 	end 	local wp_title, wp_ns 	wp_title, wp_ns, qid = _getTitleQID(qid) 	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 "".._formatResult(commonsLink,formatting).."" 	end 	-- if not consistent, fall back to search and add to tracking cat 	-- construct default result (which searches for title) 	local searchResult = "[[Commons:Special:Search/"..nsColon..(fallback or wp_title) 		.."|".._formatResult(fallback or wp_title,formatting).."]]" 	if not consistent and wp_ns == "" then 		local friendlyNS 		if nsColon == "" then 			friendlyNS = "gallery" 		else 			friendlyNS = namespace:lower() 		end 		searchResult = searchResult.."" 	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 --   search = string to search for --   fallback = string to search for if wikidata lookup fails --   formatting = formatting parameters --   qid = QID to lookup in wikidata (for testing only) -- Returns: --   formatted wikilink to Commons "best" landing page function p._getGalleryOrCategory(default, search, fallback, formatting, qid) 	if default then 		return "".._formatResult(default,formatting).."" 	end 	if search then 		return "".._formatResult(search,formatting).."" 	end 	local wp_title, wp_ns 	wp_title, wp_ns, qid = _getTitleQID(qid) 	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 "".._formatResult(galleryLink,formatting).."" 	end 	if not consistent and wp_ns == "" then 		trackingCats = "" 	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 "".._formatResult(categoryLink,formatting)..""..trackingCats 	end 	if not consistent and wp_ns == "" then 		trackingCats = trackingCats.."" 	end 	-- return search result looking for title as last attempt 	return "[[Commons:Special:Search/" .. (fallback or wp_title) .. 		"|" .. _formatResult(fallback or wp_title,formatting) .. "]]" .. trackingCats 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) --   oneSearch = only emit one search result --   formatting = formatting parameters --   qid = qid of page to lookup in wikidata (testing only) function p._getGalleryAndCategory(defaultGallery, defaultCategory,  	categoryText, oneSearch, formatting, qid 	) 	local wp_title, wp_ns 	wp_title, wp_ns, qid = _getTitleQID(qid) 	categoryText = categoryText or "category" 	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 = "" 	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.."" 	end 	local firstLink 	-- construct default result (which searches for title) 	local searchResult = "".._formatResult(wp_title,formatting).."" 	if not oneSearch then 		searchResult = searchResult.." ("..categoryText..")" 	end 	local linkText = nil 	if galleryGood then 		firstLink = galleryLink 		linkText = galleryLink 	elseif categoryGood then 		firstLink = "Category:"..categoryLink 		linkText = categoryLink 	else 		return searchResult..trackingCats 	end 	local resultVal = "".._formatResult(linkText,formatting).."" 	if galleryGood and categoryGood then 		resultVal = resultVal.." ("..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  local galleryTrackingCats = { 	commons_link_on_wikidata = , 	commons_link_defined_as_pagename = , 	commons_link_locally_defined = , 	commons_link_from_wikidata = , 	commons_link_is_pagename = , 	inconsistent =  }  local categoryTrackingCats = { 		commons_link_on_wikidata = , 		commons_link_defined_as_pagename = , 		commons_link_locally_defined = , 		commons_link_from_wikidata = , 		commons_link_is_pagename = , 		inconsistent =  	}  local function selectTrackingCat(trackingCats,wikidata,consistent,default,title) 	if not consistent then 		return trackingCats.inconsistent 	end 	if default then 	-- construct warning message 		if default == wikidata then 			return trackingCats.commons_link_on_wikidata 		end 		local warning = "" 		if wikidata then 			local generateWarning = require('Module:If preview')._warning 			warning = generateWarning({ 					"উইকিউপাত্তের সঙ্গে কমন্স সংযোগ মিলছে না" 				}) 		end 		if titleMatch(default,title) then 			return trackingCats.commons_link_defined_as_pagename .. warning 		end 		return trackingCats.commons_link_locally_defined .. warning 	end 	if wikidata then 		return trackingCats.commons_link_from_wikidata 	end 	return trackingCats.commons_link_is_pagename 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 	 	-- determine title and namespace of wikidata and wp article 	local wikidata = nil 	local consistent = nil 	-- Tracking code works for all 4 cases of states of fetchGallery/Category 	-- fetchGallery takes precedence 	if fetchGallery then 		wikidata, consistent = p._hasGalleryConsistent(qid) 		if default or not fetchCategory or (consistent and wikidata) then 			return selectTrackingCat(galleryTrackingCats,wikidata,consistent, 				                     default,title) 		end 	end     if fetchCategory then 		local cat_wikidata, cat_consistent = p._hasCategoryConsistent(qid,true) 		if not fetchGallery or (cat_consistent and cat_wikidata) then 			return selectTrackingCat(categoryTrackingCats,cat_wikidata, 			                    	 cat_consistent,default,title) 		end 		return selectTrackingCat(galleryTrackingCats,wikidata,consistent, 			                     default,title)     end 	return "" -- nothing fetched, nothing tracked end  local function _createFormatting(args) 	local formatting = {} 	formatting.linktext = args.linktext     local yesNo = require('Module:Yesno') 	formatting.lcfirst = yesNo(args.lcfirst) 	formatting.bold = yesNo(args.bold) 	formatting.italic = yesNo(args.italic) 	formatting.nowrap = yesNo(args.nowrap) 	return formatting 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.search,args.fallback,_createFormatting(args),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.search, args.fallback, _createFormatting(args), 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.search, args.fallback, _createFormatting(args), 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.categoryText, args.oneSearch, _createFormatting(args), 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