Module:UserLinks

    From TestWiki

    Documentation for this module may be created at Module:UserLinks/doc

    --------------------------------------------------------------------------------
    --                                 UserLinks                                  --
    -- This module creates a list of links about a given user. It can be used on  --
    -- its own or from a template. See the /doc page for more documentation.      --
    --------------------------------------------------------------------------------
    
    -- Require necessary modules
    local yesno = require('Module:Yesno')
    
    -- Lazily initialise modules that we might or might not need
    local mExtra -- [[Module:UserLinks/extra]]
    local mArguments -- [[Module:Arguments]]
    local mToolbar -- [[Module:Toolbar]]
    local mCategoryHandler -- [[Module:Category handler]]
    local mTableTools -- [[Module:TableTools]]
    local interwikiTable -- [[Module:InterwikiTable]], loaded with mw.loadData
    
    -- Load shared helper functions
    local mShared = require('Module:UserLinks/shared')
    local raiseError = mShared.raiseError
    local maybeLoadModule = mShared.maybeLoadModule
    local makeWikitextError = mShared.makeWikitextError
    local makeWikilink = mShared.makeWikilink
    local makeUrlLink = mShared.makeUrlLink
    local makeFullUrlLink = mShared.makeFullUrlLink
    local message = mShared.message
    
    local p = {}
    
    --------------------------------------------------------------------------------
    -- Link table
    --------------------------------------------------------------------------------
    
    function p.getLinks(snippets)
    	--[=[
    	-- Get a table of links that can be indexed with link codes. The table
    	-- returned is blank, but links are added to it on demand when it is
    	-- indexed. This is made possible by the metatable and by the various link
    	-- functions, some of which are defined here, and some of which are defined
    	-- at [[Module:UserLinks/extra]].
    	--]=]
    	local links, linkFunctions = {}, {}
    
    	----------------------------------------------------------------------------
    	-- Link functions
    	--
    	-- The following functions make the links from the link codes and the user
    	-- data snippets. New link functions should be added below the existing
    	-- functions.
    	----------------------------------------------------------------------------
    
    	function linkFunctions.u(snippets)
    		-- User page
    		return makeWikilink(
    			snippets.interwiki,
    			2,
    			snippets.username,
    			snippets.username
    		)
    	end
    
    	function linkFunctions.t(snippets)
    		-- User talk page
    		return makeWikilink(
    			snippets.interwiki,
    			3,
    			snippets.username,
    			message('display-talk')
    		)
    	end
    
    	function linkFunctions.c(snippets)
    		-- Contributions
    		return makeWikilink(
    			snippets.interwiki,
    			-1,
    			'Contribs/' .. snippets.username,
    			message('display-contributions')
    		)
    	end
    	
    	function linkFunctions.c64(snippets)
    		-- Contributions
    		local first64 = snippets.username:match('^%x+:%x+:%x+:%x+:')
    			or snippets.username:match('^%x+:%x+:%x+:')
    			or snippets.username:match('^%x+:%x+:')
    			or snippets.username:match('^%x+:')
    		return first64 and makeWikilink(
    			snippets.interwiki,
    			-1,
    			'Contribs/' .. first64 .. ':/64',
    			'(/64)'
    		) or ''
    	end
    
    	function linkFunctions.ct(snippets)
    		-- Edit count
    		return makeUrlLink(
    			{
    				host = 'xtools.wmflabs.org',
    				path = '/ec/',
    				query = {
    					username = snippets.username,
    					project = snippets.toolLang .. '.' .. snippets.projectLong .. '.org'
    				}
    			},
    			message('display-count')
    		)
    	end
    
    	function linkFunctions.m(snippets)
    		-- Page moves
    		return makeWikilink(
    			snippets.interwiki,
    			-1,
    			'Log/move/' .. snippets.username,
    			message('display-moves')
    		)
    	end
    
    	function linkFunctions.l(snippets)
    		-- Logs
    		return makeWikilink(
    			snippets.interwiki,
    			-1,
    			'Log/' .. snippets.username,
    			message('display-logs')
    		)
    	end
    
    	function linkFunctions.ae(snippets)
    		-- Automated edits (and non-automated contributions).
    		return makeUrlLink(
    			{
    				host = 'xtools.wmflabs.org',
    				path = '/autoedits/',
    				query = {
    					username = snippets.username,
    					project = snippets.toolLang .. '.' .. snippets.projectLong .. '.org'
    				}
    			},
    			message('display-autoedits')
    		)
    	end
    
    	function linkFunctions.bl(snippets)
    		-- Block log
    		return makeFullUrlLink(
    			snippets.interwiki,
    			-1,
    			'Log/block',
    			{page = 'User:' .. snippets.username},
    			message('display-blocklog')
    		)
    	end
    
    	function linkFunctions.bls(snippets)
    		-- Blocks
    		return makeWikilink(
    			snippets.interwiki,
    			-1,
    			'Log/block/' .. snippets.username,
    			message('display-blocks')
    		)
    	end
    
    	function linkFunctions.bu(snippets)
    		-- Block user
    		return makeWikilink(
    			snippets.interwiki,
    			-1,
    			'Block/' .. snippets.username,
    			message('display-blockuser')
    		)
    	end
    
    	function linkFunctions.ca(snippets)
    		-- Central auth
    		return makeWikilink(
    			snippets.interwiki,
    			-1,
    			'CentralAuth/' .. snippets.username,
    			message('display-centralauth')
    		)
    	end
    
    	function linkFunctions.dc(snippets)
    		-- Deleted contribs
    		return makeWikilink(
    			snippets.interwiki,
    			-1,
    			'DeletedContributions/' .. snippets.username,
    			message('display-deletedcontributions')
    		)
    	end
    
    	function linkFunctions.e(snippets)
    		-- Email
    		return makeWikilink(
    			snippets.interwiki,
    			-1,
    			'EmailUser/' .. snippets.username,
    			message('display-email')
    		)
    	end
    
    	function linkFunctions.es(snippets)
    		-- Edit summaries
    		return makeUrlLink(
    			{
    				host = 'xtools.wmflabs.org',
    				path = '/editsummary/',
    				query = {
    					username = snippets.username,
    					project = snippets.toolLang .. '.' .. snippets.projectLong .. '.org'
    				}
    			},
    			message('display-editsummaries')
    		)
    	end
    
    	function linkFunctions.del(snippets)
    		-- Deletions
    		return makeWikilink(
    			snippets.interwiki,
    			-1,
    			'Log/delete/' .. snippets.username,
    			message('display-deletions')
    		)
    	end
    
    	function linkFunctions.lu(snippets)
    		-- List user
    		return makeFullUrlLink(
    			snippets.interwiki,
    			-1,
    			'ListUsers',
    			{limit = 1, username = snippets.username},
    			message('display-listuser')
    		)
    	end
    
    	function linkFunctions.sul(snippets)
    		-- SUL
    		return makeWikilink(
    			nil,
    			nil,
    			'sulutil:' .. snippets.username,
    			message('display-sul')
    		)
    	end
    
    	function linkFunctions.tl(snippets)
    		-- Target logs
    		return makeFullUrlLink(
    			snippets.interwiki,
    			-1,
    			'Log',
    			{page = mw.site.namespaces[2].name .. ':' .. snippets.username},
    			message('display-targetlogs')
    		)
    	end
    
    	function linkFunctions.efl(snippets)
    		-- Edit filter log
    		return makeFullUrlLink(
    			snippets.interwiki,
    			-1,
    			'AbuseLog',
    			{wpSearchUser = snippets.username},
    			message('display-abuselog')
    		)
    	end
    
    	function linkFunctions.pr(snippets)
    		-- Protections
    		return makeWikilink(
    			snippets.interwiki,
    			-1,
    			'Log/protect/' .. snippets.username,
    			message('display-protections')
    		)
    	end
    
    	function linkFunctions.rl(snippets)
    		-- User rights
    		return makeWikilink(
    			snippets.interwiki,
    			-1,
    			'Log/rights/' .. snippets.username,
    			message('display-rights')
    		)
    	end
    
    	function linkFunctions.ren(snippets)
    		-- Renames
    		return makeWikilink(
    			snippets.interwiki,
    			-1,
    			'Log/renameuser/' .. snippets.username,
    			message('display-renames')
    		)
    	end
    
    	function linkFunctions.rfa(snippets)
    		-- Requests for adminship
    		return makeWikilink(
    			nil,
    			-1,
    			'PrefixIndex/' .. message('page-rfa') .. '/' .. snippets.username,
    			message('display-rfa')
    		)
    	end
    
    	function linkFunctions.api(snippets)
    		-- API user data
    		return makeUrlLink(
    			{
    				host = snippets.fullDomain,
    				path = '/w/api.php',
    				query = {
    					action = 'query',
    					list = 'users',
    					usprop = 'groups|editcount',
    					ususers = snippets.username
    				}
    			},
    			message('display-api')
    		)
    	end
    
    	function linkFunctions.up(snippets)
    		-- Uploads
    		return makeWikilink(
    			snippets.interwiki,
    			-1,
    			'ListFiles/' .. snippets.username,
    			message('display-uploads')
    		)
    	end
    	
    	----------------------------------------------------------------------------
    	-- End of link functions
    	----------------------------------------------------------------------------
    
    	-- Define the metatable that memoizes the link functions, and fetches link
    	-- functions from [[Module:UserLinks/extra]] if necessary.
    
    	-- Lazily initialise the extraLinkFunctions table. We only want to load
    	-- [[Module:UserLinks/extra]] as necessary, so it has a low transclusion
    	-- count.
    	local extraLinkFunctions
    
    	-- Define functions for shared code in the metatable.
    	local function validateCode(code)
    		-- Checks whether code is a valid link code - i.e. checks that it is a
    		-- string and that it is not the blank string. Returns the code if
    		-- the check passes, and nil if not.
    		if type(code) == 'string' and code ~= '' then
    			return code
    		else
    			return nil
    		end
    	end
    
    	local function getExtraLinkFunctions()
    		-- Loads the table of extra link functions from the /extra module.
    		-- If there is a problem with loading it, return false. We use the
    		-- distinction between false and nil to record whether we have already
    		-- tried to load it.
    		if extraLinkFunctions ~= nil then
    			return extraLinkFunctions
    		end
    		if mExtra == nil then
    			-- If loading the module fails, maybeLoadModule returns false.
    			-- Here we use the distinction between false and nil to record
    			-- whether we have already tried to load the /extra module.
    			mExtra = maybeLoadModule('Module:UserLinks/extra')
    		end
    		if type(mExtra) == 'table'
    			and type(mExtra.linkFunctions) == 'table'
    		then
    			extraLinkFunctions = mExtra.linkFunctions
    		else
    			extraLinkFunctions = false
    		end
    		return extraLinkFunctions
    	end
    
    	local function memoizeExtraLink(code, func)
    		local success, link = pcall(func, snippets)
    		if success and type(link) == 'string' then
    			links[code] = link
    			return link
    		end
    		return nil
    	end
    
    	-- Define the metatable.
    	setmetatable(links, {
    		__index = function (t, key)
    			local code = validateCode(key)
    			if not code then
    				raiseError(
    					message('error-malformedlinkcode'),
    					message('error-malformedlinkcode-section')
    				)
    			end
    			local linkFunction = linkFunctions[code]
    			local link
    			if linkFunction then
    				link = linkFunction(snippets)
    				links[code] = link
    			else
    				extraLinkFunctions = getExtraLinkFunctions()
    				if extraLinkFunctions then
    					local extraLinkFunction = extraLinkFunctions[code]
    					if type(extraLinkFunction) == 'function' then
    						link = memoizeExtraLink(code, extraLinkFunction)
    					end
    				end
    			end
    			if link then
    				return link
    			else
    				raiseError(
    					message('error-invalidlinkcode', code),
    					message('error-invalidlinkcode-section')
    				)
    			end
    		end,
    		__pairs = function ()
    			extraLinkFunctions = getExtraLinkFunctions()
    			if extraLinkFunctions then
    				for code, func in pairs(extraLinkFunctions) do
    					if validateCode(code) and type(func) == 'function' then
    						memoizeExtraLink(code, func)
    					end
    				end
    			end
    			-- Allow built-in functions to overwrite extra functions.
    			for code, func in pairs(linkFunctions) do
    				local link = func(snippets)
    				links[code] = link
    			end
    			return function (t, key)
    				return next(links, key)
    			end
    		end
    	})
    	return links
    end
    
    --------------------------------------------------------------------------------
    -- User data snippets
    --------------------------------------------------------------------------------
    
    function p.getSnippets(args)
    	--[=[
    	-- This function gets user data snippets from the arguments, and from
    	-- [[Module:InterwikiTable]]. The data is loaded as necessary and memoized
    	-- in the snippets table for performance. 
    	--
    	-- Snippets default to the blank string, '', so they can be used in
    	-- concatenation operations without coders having to worry about raising
    	-- errors. Because of this, the local functions snippetExists and
    	-- getSnippet have been written to aid people writing new snippets. These
    	-- functions treat the blank string as false. It is not necessary to return
    	-- the blank string from a snippet function, as nil and false values are
    	-- automatically converted into the blank string by the metatable.
    	--
    	-- If you add a new snippet, please document it at
    	-- [[Module:UserLinks#Adding new links]].
    	--]=]
    	local snippets, snippetFunctions = {}, {}
    	setmetatable(snippets, {
    		__index = function (t, key)
    			local snippetFunction = snippetFunctions[key]
    			if snippetFunction then
    				snippets[key] = snippetFunction() or ''
    				return snippets[key]
    			else
    				raiseError(
    					message('error-nosnippet', key),
    					message('error-nosnippet-section')
    				)
    			end
    		end
    	})
    
    	-- Define helper functions for writting the snippet functions.
    	local function snippetExists(key)
    		-- We have set the metatable up to make snippets default to '', so we
    		-- don't have to test for false or nil.
    		return snippets[key] ~= ''
    	end
    
    	local function getSnippet(key)
    		local ret = snippets[key]
    		if ret == '' then
    			return nil
    		else
    			return ret
    		end
    	end
    
    	-- Start snippet functions.
    
    	function snippetFunctions.username()
    		-- The username.
    		local username = args.user or args.User
    		return username or raiseError(
    			message('error-nousername'),
    			message('error-nousername-section')
    		)
    	end
    
    	function snippetFunctions.usernameHtml()
    		-- The username html-encoded. Spaces are encoded as pluses.
    		return mw.uri.encode(snippets.username)
    	end
    
    	function snippetFunctions.project()
    		-- The project name.
    		-- Also does the work for snippetFunctions.interwikiTableKey, and adds
    		-- the project value to snippets.lang if it is a valid language code.
    		local project = args.Project or args.project
    		if not project then
    			return nil
    		end
    		local projectValidated, interwikiTableKey = p.validateProjectCode(project)
    		if not projectValidated then
    			if mw.language.isKnownLanguageTag(project) then
    				if not snippetExists('lang') then
    					snippets.lang = project
    				end
    			else
    				raiseError(
    					message('error-invalidproject', project),
    					message('error-invalidproject-section')
    				)
    			end
    		end
    		snippets.interwikiTableKey = interwikiTableKey
    		return project
    	end
    
    	function snippetFunctions.interwikiTableKey()
    		-- The key for the project in Module:InterwikiTable.
    		-- Relies on snippetFunctions.project to do the real work.
    		local temp = snippets.project -- required; puts key in snippets table
    		return rawget(snippets, 'interwikiTableKey')
    	end
    
    	function snippetFunctions.toolProject()
    		-- The short project code for use with toolserver or labs. It is always
    		-- present, even if the "project" argument is absent. The default value
    		-- is the "snippet-project-default" message.
    		local project = getSnippet('project')
    		if not project then
    			return message('snippet-project-default')
    		else
    			return project
    		end
    	end
    
    	function snippetFunctions.projectLong()
    		-- The long form of the project name, e.g. "wikipedia" or "wikibooks".
    		local key = getSnippet('interwikiTableKey')
    		if not key then
    			return message('snippet-projectlong-default')
    		end
    		interwikiTable = interwikiTable or mw.loadData('Module:InterwikiTable')
    		local prefixes = interwikiTable[key].iw_prefix
    		-- Using prefixes[2] is a bit of a hack, but should find the long name
    		-- most of the time.
    		return prefixes[2] or prefixes[1] 
    	end
    
    	function snippetFunctions.lang()
    		-- The language code.
    		local lang = args.lang or args.Lang
    		if not lang then
    			return nil
    		end
    		if mw.language.isKnownLanguageTag(lang) then
    			return lang
    		else
    			raiseError(
    				message('error-invalidlanguage', lang),
    				message('error-invalidlanguage-section')
    			)
    		end
    	end
    
    	function snippetFunctions.toolLang()
    		-- The language code for use with toolserver or labs tools. It is always
    		-- present, even if the "lang" argument is absent. The default value is
    		-- the "snippet-lang-default" message. 
    		return getSnippet('lang') or message('snippet-lang-default')
    	end
    
    	function snippetFunctions.interwiki()
    		-- The interwiki prefix, consisting of the project and language values,
    		-- separated by colons, e.g. ":wikt:es:".
    		local project = getSnippet('project')
    		local lang = getSnippet('lang')
    		if not project and not lang then
    			return nil
    		end
    		local ret = {}
    		ret[#ret + 1] = project
    		ret[#ret + 1] = lang
    		return table.concat(ret, ':')
    	end
    
    	function snippetFunctions.fullDomain()
    		-- The full domain name of the site, e.g. www.mediawiki.org,
    		-- en.wikpedia.org, or ja.wikibooks.org.
    		local fullDomain
    		local lang = getSnippet('toolLang')
    		local key = getSnippet('interwikiTableKey')
    		if key then
    			interwikiTable = interwikiTable or mw.loadData('Module:InterwikiTable')
    			local domain = interwikiTable[key].domain
    			local takesLangPrefix = interwikiTable[key].takes_lang_prefix
    			if takesLangPrefix then
    				fullDomain = lang .. '.' .. domain
    			else
    				fullDomain = domain
    			end
    		else
    			fullDomain = lang .. '.wikipedia.org'
    		end
    		return fullDomain
    	end
    
    	-- End snippet functions. If you add a new snippet function, please
    	-- document it at [[Module:UserLinks#Adding new links]].
    
    	return snippets
    end 
    
    function p.validateProjectCode(s)
    	-- Validates a project code, by seeing whether it is present in
    	-- [[Module:InterwikiTable]]. If it is present, returns the code and the
    	-- InterwikiTable key for the corresponding site. If not present,
    	-- returns nil for both.
    	interwikiTable = interwikiTable or mw.loadData('Module:InterwikiTable')
    	for key, t in pairs(interwikiTable) do
    		for i, prefix in ipairs(t.iw_prefix) do
    			if s == prefix then
    				return s, key
    			end
    		end
    	end
    	return nil, nil
    end
    
    --------------------------------------------------------------------------------
    -- Main functions
    --------------------------------------------------------------------------------
    
    local function makeInvokeFunction(funcName)
    	-- Makes a function that can be accessed from #invoke. This is only required
    	-- for functions that need to access arguments.
    	return function (frame)
    		mArguments = require('Module:Arguments')
    		local args = mArguments.getArgs(frame)
    		return p[funcName](args)
    	end
    end
    
    p.main = makeInvokeFunction('_main')
    
    function p._main(args)
    	-- The main function. This is the one called from [[Template:User-multi]],
    	-- via p.main.
    	local options = p.getOptions(args)
    	local snippets = p.getSnippets(args)
    	local codes = p.getCodes(args)
    	local links = p.getLinks(snippets)
    	-- Overload the built-in Lua error function to generate wikitext errors
    	-- meant for end users to see. This makes things harder to debug when
    	-- real errors occur, but it is the only realistic way to show wikitext
    	-- errors and and still have sane code when using metatables, etc.
    	local success, result = pcall(p.export, codes, links, options)
    	if success then
    		return result
    	else
    		return makeWikitextError(result, options.isDemo)
    	end
    end
    
    function p.getOptions(args)
    	-- Gets the options from the args table, so that we don't have to pass
    	-- around the whole args table all the time.
    	local options = {}
    	options.isDemo = yesno(args.demo) or false
    	options.toolbarStyle = yesno(args.small) and 'font-size: 90%;' or nil
    	options.sup = yesno(args.sup, true)
    	options.separator = args.separator
    	options.span = args.span
    	return options
    end
    
    function p.getCodes(args)
    	-- Gets the link codes from the arguments. The codes aren't validated
    	-- at this point.
    	mTableTools = maybeLoadModule('Module:TableTools')
    	local codes
    	if mTableTools then
    		codes = mTableTools.compressSparseArray(args)
    	else
    		codes = {}
    		for i, code in ipairs(args) do
    			codes[i] = code
    		end
    	end
    	return codes
    end
    
    function p.export(codes, links, options)
    	-- Make the user link.
    	local userLink = links.u
    
    	-- If we weren't passed any link codes, just return the user link.
    	if #codes < 1 then
    		return userLink
    	end
    
    	-- Make the toolbar.
    	mToolbar = require('Module:Toolbar')
    	local toolbarArgs = {}
    	for i, code in ipairs(codes) do
    		local link = links[code]
    		toolbarArgs[#toolbarArgs + 1] = link
    	end
    	toolbarArgs.style = options.toolbarStyle
    	toolbarArgs.separator = options.separator or 'dot'
    	toolbarArgs.span = options.span
    	local toolbar = mToolbar.main(toolbarArgs)
    
    	-- Apply the sup option.
    	if options.sup then
    		toolbar = '<sup>' .. toolbar .. '</sup>'
    	end
    	
    	-- If we are transcluding, add a non-breaking space, but if we are substing
    	-- just use a normal space
    	local space = mw.isSubsting() and ' ' or '&nbsp;'
    	
    	return userLink .. space .. toolbar
    end
    
    --------------------------------------------------------------------------------
    -- Single link function
    --------------------------------------------------------------------------------
    
    p.single = makeInvokeFunction('_single')
    
    function p._single(args)
    	-- Fetches a single link from the link table.
    	local options = p.getOptions(args)
    	local snippets = p.getSnippets(args)
    	local links = p.getLinks(snippets)
    	local code = args[1]
    	local success, link = pcall(p.exportSingle, links, code)
    	if success then
    		return link
    	else
    		return makeWikitextError(link, options.isDemo)
    	end
    end
    
    function p.exportSingle(links, code)
    	-- If any errors occur, they will probably occur here. This function
    	-- exists purely so that all the errors that will occur in p._single can
    	-- be handled using a single pcall.
    	if not code then
    		raiseError(
    			message('error-nolinkcode'),
    			message('error-nolinkcode-section')
    		)
    	end
    	return links[code]
    end
    
    --------------------------------------------------------------------------------
    -- Link table
    --------------------------------------------------------------------------------
    
    function p.linktable()
    	-- Returns a wikitext table of link codes, with an example link for each
    	-- one. This function doesn't take any arguments, so it can be accessed
    	-- directly from wiki pages without using makeInvokeFunction.
    	local args = {user = 'Example'}
    	local snippets = p.getSnippets(args)
    	local links = p.getLinks(snippets)
    
    	-- Assemble the codes and links in order
    	local firstCodes = {'u', 't', 'c'}
    	local firstLinks, firstCodesKeys = {}, {}
    	for i, code in ipairs(firstCodes) do
    		firstCodesKeys[code] = true
    		firstLinks[#firstLinks + 1] = {code, links[code]}
    	end
    	local secondLinks = {}
    	for code, link in pairs(links) do
    		if not firstCodesKeys[code] then
    			secondLinks[#secondLinks + 1] = {code, link}
    		end
    	end
    	table.sort(secondLinks, function(t1, t2)
    		return t1[1] < t2[1]
    	end)
    	local links = {}
    	for i, t in ipairs(firstLinks) do
    		links[#links + 1] = t
    	end
    	for i, t in ipairs(secondLinks) do
    		links[#links + 1] = t
    	end
    
    	-- Output the code table in table format
    	local ret = {}
    	ret[#ret + 1] = '{| class="wikitable plainlinks sortable"'
    	ret[#ret + 1] = '|-'
    	ret[#ret + 1] = '! ' .. message('linktable-codeheader')
    	ret[#ret + 1] = '! ' .. message('linktable-previewheader')
    	for i, t in ipairs(links) do
    		local code = t[1]
    		local link = t[2]
    		ret[#ret + 1] = '|-'
    		ret[#ret + 1] = "| '''" .. code .. "'''" 
    		ret[#ret + 1] = '| ' .. link
    	end
    	ret[#ret + 1] = '|}'
    	return table.concat(ret, '\n')
    end 
    
    return p