Difference between revisions of "Module:UserLinks"

From blackwiki
Jump to navigation Jump to search
blackwiki>Mr. Stradivarius
(update count link per protected edit request by User:Frietjes)
blackwiki>Mr. Stradivarius
(Complete rewrite of the module, updated from sandbox. Now supports adding new link functions on Module:UserLinks/extra, and is configurable at Module:UserLinks/config.)
Line 1: Line 1:
local ToolbarBuilder = require('Module:Toolbar')
+
--------------------------------------------------------------------------------
local interwikiTable = mw.loadData("Module:InterwikiTable")
+
--                                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.      --
 +
--------------------------------------------------------------------------------
  
local u = {} -- Table for user-data helper strings.
+
-- Require necessary modules
local trackingCategories = {} -- Table for storing the tracking categories.
+
local yesno = require('Module:Yesno')
local demo
 
  
-- Define a custom error message for this module.
+
-- Lazily initialise modules that we might or might not need
local function err(msg, section)
+
local mExtra -- [[Module:UserLinks/extra]]
    local help
+
local mArguments -- [[Module:Arguments]]
    if section then
+
local mToolbar -- [[Module:Toolbar]]
        help = ' ([[Template:User-multi#' .. section .. '|help]])'
+
local mCategoryHandler -- [[Module:Category handler]]
    else
+
local mTableTools -- [[Module:TableTools]]
        help = ''
+
local interwikiTable -- [[Module:InterwikiTable]], loaded with mw.loadData
    end
+
 
    local cat
+
-- Load shared helper functions
    if demo == 'yes' then
+
local mShared = require('Module:UserLinks/shared')
        cat = ''
+
local raiseError = mShared.raiseError
    else
+
local maybeLoadModule = mShared.maybeLoadModule
        cat = '[[Category:UserLinks transclusions with errors]]'
+
local makeWikitextError = mShared.makeWikitextError
    end
+
local makeWikilink = mShared.makeWikilink
    return '<span class="error">[[Template:User-multi|User-multi]] error: ' .. msg
+
local makeUrlLink = mShared.makeUrlLink
        .. help .. '.</span>' .. cat
+
local makeFullUrlLink = mShared.makeFullUrlLink
end
+
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.
 +
--
 +
-- For documentation on how to add new link functions, please see
 +
-- [[Module:UserLinks#Adding new links]].
 +
----------------------------------------------------------------------------
 +
 
 +
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,
 +
'Contributions/' .. snippets.username,
 +
message('display-contributions')
 +
)
 +
end
 +
 
 +
function linkFunctions.ct(snippets)
 +
-- Edit count
 +
return makeUrlLink(
 +
{
 +
host = 'tools.wmflabs.org',
 +
path = '/supercount/index.php',
 +
query = {
 +
user = snippets.username,
 +
project = snippets.toolLang .. '.' .. snippets.projectLong
 +
}
 +
},
 +
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.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 = 'tools.wmflabs.org',
 +
path = '/xtools/editsummary/index.php',
 +
query = {
 +
name = snippets.username,
 +
lang = snippets.toolLang,
 +
wiki = snippets.projectLong
 +
}
 +
},
 +
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, user = 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)
--      To add more link types, write a function that produces an individual link, and put  --
+
-- Edit filter log
--      it at the bottom of the list below. Then, add a link code for your function to the  --
+
return makeFullUrlLink(
--      "linktypes" table. Try and make the code three letters or less. There are a number  --
+
snippets.interwiki,
--     of helper strings available for writing the functions:                      --
+
-1,
--                                                                                          --
+
'AbuseLog',
--      u.username          The plain username. If the username is not present then the    --
+
{wpSearchUser = snippets.username},
--                          module returns an error.                                        --
+
message('display-abuselog')
--      u.usernameHtml      The username html-encoded. Spaces are encoded with plus signs.  --
+
)
--      u.project          The project name. Nil if not specified.                        --
+
end
--      u.lang              The language code. Nil if not specified.                        --
 
--      u.interwiki         The interwiki prefix, consisting of the project and language    --
 
--                          values, separated by colons, e.g. ":wikt:es:". If no project    --
 
--                          or language values are found, this is the blank string, "".    --
 
--      u.projectCode      If a valid project is specified, this is the code for that      --
 
--                          project in [[Module:InterwikiTable]]. Otherwise this is nil.    --
 
--      u.projectLong      The long project name, e.g. "wikipedia" or "wikibooks". If      --
 
--                          not specified the default is "wikipedia".                      --
 
--      u.toolLang          The language code for use with toolserver or labs tools. The    --
 
--                          default is "en".                                                --
 
--                                                                                          --
 
--      If you want more helper strings, you can define them in the generateUserDataStrings --
 
--      function below.                                                                    --
 
----------------------------------------------------------------------------------------------
 
  
----------------------------------------------------------------------------------------------
+
function linkFunctions.pr(snippets)
--                                LINK FUNCTIONS START                                      --
+
-- Protections
----------------------------------------------------------------------------------------------
+
return makeWikilink(
 +
snippets.interwiki,
 +
-1,
 +
'Log/protect/' .. snippets.username,
 +
message('display-protections')
 +
)
 +
end
  
local function makeUserLink()
+
function linkFunctions.rl(snippets)
    return '[[' .. u.interwiki .. 'User:' .. u.username .. '|' .. u.username .. ']]'
+
-- User rights
end
+
return makeWikilink(
 +
snippets.interwiki,
 +
-1,
 +
'Log/rights/' .. snippets.username,
 +
message('display-rights')
 +
)
 +
end
  
local function makeTalkLink()
+
function linkFunctions.ren(snippets)
    return '[[' .. u.interwiki .. 'User talk:' .. u.username .. '|talk]]'
+
-- Renames
end
+
return makeWikilink(
 +
snippets.interwiki,
 +
-1,
 +
'Log/renameuser/' .. snippets.username,
 +
message('display-renames')
 +
)
 +
end
  
local function makeContribsLink()
+
function linkFunctions.rfa(snippets)
    return '[[' .. u.interwiki .. 'Special:Contributions/' .. u.username .. '|contribs]]'
+
-- Requests for adminship
end
+
return makeWikilink(
 +
nil,
 +
-1,
 +
'PrefixIndex/' .. message('page-rfa') .. '/' .. snippets.username,
 +
message('display-rfa')
 +
)
 +
end
  
local function makeCountLink()
+
function linkFunctions.api(snippets)
    return '[//tools.wmflabs.org/supercount/index.php?user=' .. u.usernameHtml
+
-- API user data
        .. '&project=' .. u.toolLang .. '.' .. u.projectLong
+
return makeUrlLink(
        .. ' count]'
+
{
end
+
host = snippets.fullDomain,
 +
path = '/w/api.php',
 +
query = {
 +
action = 'query',
 +
list = 'users',
 +
usprop = 'editcount',
 +
ususers = snippets.username
 +
}
 +
},
 +
message('display-api')
 +
)
 +
end
  
local function makeMovesLink()
+
function linkFunctions.up(snippets)
    return '[[' .. u.interwiki .. 'Special:Log/move/' .. u.username .. '|page&nbsp;moves]]'
+
-- Uploads
end
+
return makeWikilink(
 +
snippets.interwiki,
 +
-1,
 +
'ListFiles/' .. snippets.username,
 +
message('display-uploads')
 +
)
 +
end
 +
 +
----------------------------------------------------------------------------
 +
-- End of link functions
 +
----------------------------------------------------------------------------
  
local function makeLogsLink()
+
-- Define the metatable that memoizes the link functions, and fetches link
    return '[[' .. u.interwiki .. 'Special:Log/' .. u.username .. '|logs]]'
+
-- functions from [[Module:UserLinks/extra]] if necessary.
end
 
  
local function makeBlockLogLink()
+
-- Lazily initialise the extraLinkFunctions table. We only want to load
    local url = mw.uri.fullUrl(u.interwiki .. 'Special:Log/block', 'page=User:' .. u.usernameHtml)
+
-- [[Module:UserLinks/extra]] as necessary, so it has a low transclusion
    return '[' .. tostring(url) .. ' block&nbsp;log]'
+
-- count.
end
+
local extraLinkFunctions
  
local function makeBlocksLink()
+
-- Define functions for shared code in the metatable.
    return '[[' .. u.interwiki .. 'Special:Log/block/' .. u.username .. '|blocks]]'
+
local function validateCode(code)
end
+
-- 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 makeBlockUserLink()
+
local function getExtraLinkFunctions()
    return '[[' .. u.interwiki .. 'Special:Block/' .. u.username .. '|block&nbsp;user]]'
+
-- Loads the table of extra link functions from the /extra module.
end
+
-- 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 makeCentralAuthLink()
+
local function memoizeExtraLink(code, func)
    return '[[' .. u.interwiki .. 'Special:CentralAuth/' .. u.username .. '|central&nbsp;auth]]'
+
local success, link = pcall(func, snippets)
end
+
if success and type(link) == 'string' then
 +
links[code] = link
 +
return link
 +
end
 +
return nil
 +
end
  
local function makeDeletedContribsLink()
+
-- Define the metatable.
    return '[[' .. u.interwiki .. 'Special:DeletedContributions/' .. u.username .. '|deleted&nbsp;contribs]]'
+
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
 
end
  
local function makeEmailLink()
+
--------------------------------------------------------------------------------
    return '[[' .. u.interwiki .. 'Special:Emailuser/' .. u.username .. '|email]]'
+
-- User data snippets
end
+
--------------------------------------------------------------------------------
  
local function makeEditSummariesLink()
+
function p.getSnippets(args)
    return '[http://tools.wmflabs.org/xtools/editsummary/index.php?name=' .. u.usernameHtml
+
--[=[
        .. '&lang=' .. u.toolLang
+
-- This function gets user data snippets from the arguments, and from
        .. '&wiki=' .. u.projectLong
+
-- [[Module:InterwikiTable]]. The data is loaded as necessary and memoized
        .. ' edit&nbsp;summaries]'
+
-- in the snippets table for performance.  
end
+
--
 +
-- 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
 +
})
  
local function makeDeletionsLink()
+
-- Define helper functions for writting the snippet functions.
    return '[[' .. u.interwiki .. 'Special:Log/delete/' .. u.username .. '|deletions]]'
+
local function snippetExists(key)
end
+
-- 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 makeListUserLink()
+
local function getSnippet(key)
    local url = mw.uri.fullUrl(u.interwiki .. 'Special:ListUsers', 'limit=1&username=' .. u.usernameHtml)
+
local ret = snippets[key]
    return '[' .. tostring(url) .. ' list&nbsp;user]'
+
if ret == '' then
end
+
return nil
 +
else
 +
return ret
 +
end
 +
end
  
local function makeSulLink()
+
-- Start snippet functions.
    return '[[sulutil:' .. u.username .. '|global&nbsp;contribs]]'
 
end
 
  
local function makeTargetLogsLink()
+
function snippetFunctions.username()
    local url = mw.uri.fullUrl(u.interwiki .. 'Special:Log', 'page=User:' .. u.usernameHtml)
+
-- The username.
    return '[' .. tostring(url) .. ' target&nbsp;logs]'
+
local username = args.user or args.User
end
+
return username or raiseError(
 +
message('error-nousername'),
 +
message('error-nousername-section')
 +
)
 +
end
  
local function makeEditFilterLogLink()
+
function snippetFunctions.usernameHtml()
    local url = mw.uri.fullUrl(u.interwiki .. 'Special:AbuseLog', 'wpSearchUser=' .. u.usernameHtml)
+
-- The username html-encoded. Spaces are encoded as pluses.
    return '[' .. tostring(url) .. ' edit&nbsp;filter&nbsp;log]'
+
return mw.uri.encode(snippets.username)
end
+
end
  
local function makeProtectionsLink()
+
function snippetFunctions.project()
    return '[[' .. u.interwiki .. 'Special:Log/protect/' .. u.username .. '|protections]]'
+
-- The project name.
end
+
-- 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
  
local function makeRightsLink()
+
function snippetFunctions.interwikiTableKey()
    return '[[' .. u.interwiki .. 'Special:Log/rights/' .. u.username .. '|rights]]'
+
-- The key for the project in Module:InterwikiTable.
end
+
-- Relies on snippetFunctions.project to do the real work.
 +
local temp = snippets.project -- required; puts key in snippets table
 +
return rawget(snippets, 'interwikiTableKey')
 +
end
  
local function makeRenamesLink()
+
function snippetFunctions.toolProject()
    return '[[' .. u.interwiki .. 'Special:Log/renameuser/' .. u.username .. '|renames]]'
+
-- The short project code for use with toolserver or labs. It is always
end
+
-- 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
  
local function makeRfaLink()
+
function snippetFunctions.projectLong()
    if u.project or u.lang then
+
-- The long form of the project name, e.g. "wikipedia" or "wikibooks".
        table.insert( trackingCategories, '[[Category:UserLinks transclusions with unresolvable interwiki links]]' )
+
local key = getSnippet('interwikiTableKey')
    end
+
if not key then
    return '[[Special:PrefixIndex/Wikipedia:Requests for adminship/' .. u.username .. '|RfA]]'
+
return message('snippet-projectlong-default')
end
+
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
  
local function makeApiLink()
+
function snippetFunctions.lang()
    -- Find the full domain, as the API can't be accessed through the interwiki system.
+
-- The language code.
    local fulldomain
+
local lang = args.lang or args.Lang
    local lang = u.lang or 'en'
+
if not lang then
    if u.projectCode then
+
return nil
        local domain = interwikiTable[u.projectCode].domain
+
end
        local takesLangPrefix = interwikiTable[u.projectCode].takes_lang_prefix
+
if mw.language.isKnownLanguageTag(lang) then
        if not takesLangPrefix then
+
return lang
            fulldomain = domain
+
else
        else
+
raiseError(
            fulldomain = lang .. '.' .. domain
+
message('error-invalidlanguage', lang),
        end
+
message('error-invalidlanguage-section')
    else
+
)
        fulldomain = lang .. '.wikipedia.org'
+
end
    end
+
end
   
 
    -- Return the API link
 
    return '[//' .. fulldomain .. '/w/api.php?action=query&list=users&usprop=editcount&ususers=' .. u.usernameHtml .. ' api]'
 
end
 
  
local function makeUploadsLink()
+
function snippetFunctions.toolLang()
    return '[[' .. u.interwiki .. 'Special:ListFiles/' .. u.username .. '|uploads]]'
+
-- The language code for use with toolserver or labs tools. It is always
end
+
-- 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()
--                                   LINK FUNCTIONS END                                    --
+
-- The interwiki prefix, consisting of the project and language values,
--     To enable new link functions, add the code to the "linktypes" table directly below. --
+
-- 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
  
local linktypes = {
+
function snippetFunctions.fullDomain()
    {'t'  , makeTalkLink},
+
-- The full domain name of the site, e.g. www.mediawiki.org,
    {'c'  , makeContribsLink},
+
-- en.wikpedia.org, or ja.wikibooks.org.
    {'ct'  , makeCountLink},
+
local fullDomain
    {'m'   , makeMovesLink},
+
local lang = getSnippet('toolLang')
    {'l'   , makeLogsLink},
+
local key = getSnippet('interwikiTableKey')
    {'bl'  , makeBlockLogLink},
+
if key then
    {'bls' , makeBlocksLink},
+
interwikiTable = interwikiTable or mw.loadData('Module:InterwikiTable')
    {'bu'  , makeBlockUserLink},
+
local domain = interwikiTable[key].domain
    {'ca'  , makeCentralAuthLink},
+
local takesLangPrefix = interwikiTable[key].takes_lang_prefix
    {'dc'  , makeDeletedContribsLink},
+
if takesLangPrefix then
    {'e'  , makeEmailLink},
+
fullDomain = lang .. '.' .. domain
    {'es' , makeEditSummariesLink},
+
else
    {'del' , makeDeletionsLink},
+
fullDomain = domain
    {'lu'  , makeListUserLink},
+
end
    {'sul' , makeSulLink},
+
else
    {'tl'  , makeTargetLogsLink},
+
fullDomain = lang .. '.wikipedia.org'
    {'efl' , makeEditFilterLogLink},
+
end
    {'pr' , makeProtectionsLink},
+
return fullDomain
    {'rl'  , makeRightsLink},
+
end
    {'ren' , makeRenamesLink},
 
    {'rfa' , makeRfaLink},
 
    {'api' , makeApiLink},
 
    {'up'  , makeUploadsLink}
 
}
 
  
local function getLink(linktype)
+
-- End snippet functions. If you add a new snippet function, please
    local linkNumber
+
-- document it at [[Module:UserLinks#Adding new links]].
    for i, value in ipairs(linktypes) do
 
        if value[1] == linktype then
 
            linkNumber = i
 
            break
 
        end
 
    end
 
    if not linkNumber then
 
        return err('"' .. linktype .. '" is not a valid link code', 'Not a valid link code')
 
    end
 
    local result = linktypes[linkNumber][2]()
 
    if type(result) ~= 'string' then
 
        return err(
 
            'the function for code "' .. linktype .. '" did not return a string value',
 
            'Function did not return a string value'
 
        )
 
    end
 
    return result
 
end
 
  
local function makeToolbar(args)
+
return snippets
    local targs = {}
+
end
    local numArgsExist = false
 
    for k, v in pairs(args) do
 
        if type(k) == 'number' then
 
            numArgsExist = true
 
            targs[k] = getLink(v)
 
        end
 
    end
 
    targs.style = args.small and 'font-size: 90%;'
 
    targs.separator = args.separator or 'dot'
 
   
 
    if numArgsExist == false then
 
        return nil -- Don't return a toolbar if no numeric arguments exist.
 
    else
 
        return ToolbarBuilder.main(targs)
 
    end
 
end
 
  
-- This function finds whether a string is a valid interwiki project prefix.
+
function p.validateProjectCode(s)
-- If the string is valid, the function outputs two values: true, and the site code
+
-- Validates a project code, by seeing whether it is present in
-- used in [[Module:InterwikiTable]]. If the string is valid, the function outputs
+
-- [[Module:InterwikiTable]]. If it is present, returns the code and the
-- false and nil.
+
-- InterwikiTable key for the corresponding site. If not present,
local function isKnownProject(prefix)
+
-- returns nil for both.
     for projectCode, projectVal in pairs(interwikiTable) do
+
interwikiTable = interwikiTable or mw.loadData('Module:InterwikiTable')
         for _, iwCode in ipairs(projectVal.iw_prefix) do
+
     for key, t in pairs(interwikiTable) do
             if iwCode == prefix then
+
         for i, prefix in ipairs(t.iw_prefix) do
                 return true, projectCode
+
             if s == prefix then
 +
                 return s, key
 
             end
 
             end
 
         end
 
         end
 
     end
 
     end
     return false, nil
+
     return nil, nil
 
end
 
end
  
local function generateUserDataStrings(args)
+
--------------------------------------------------------------------------------
    -- If the username is absent or blank, return an error and a tracking category.
+
-- Main functions
    if args.user == '' or (not args.user and (not args.User or args.User == '')) then
+
--------------------------------------------------------------------------------
        return err('no username detected', 'No username detected')
 
    else
 
        u.username = args.user or args.User
 
    end
 
   
 
    -- Get other basic user data strings.
 
    u.project = args.Project or args.project
 
    u.lang = args.lang or args.Lang
 
    if u.lang then
 
        if mw.language.isKnownLanguageTag(u.lang) then
 
            table.insert(trackingCategories, '[[Category:UserLinks transclusions with language parameters]]')
 
        else
 
            return err('"' .. u.lang .. '" is not a valid language code', 'Not a valid language code')
 
        end
 
    end
 
   
 
    -- Process the project value if it is present.
 
    if u.project then
 
        table.insert( trackingCategories, '[[Category:UserLinks transclusions with project parameters]]' )
 
       
 
        -- If u.project is a known project, we only need to get the project code. If the project
 
        -- isn't known, first check whether it is a valid language code, and if not then see if it's
 
        -- an interwiki code separated by colons, e.g. "wikt:es".
 
        local uprojectIsKnownProject, uprojectProjectCode = isKnownProject(u.project)
 
        if uprojectIsKnownProject then
 
            u.projectCode = uprojectProjectCode
 
        else
 
            if mw.language.isKnownLanguageTag(u.project) then
 
                u.lang = u.project
 
                u.project = nil
 
            else
 
                local pref1, pref2 = mw.ustring.match( u.project, '^(%w+):(%w+)$' )
 
                if pref1 and pref2 then
 
                    local pref1IsKnownProject, pref1ProjectCode = isKnownProject(pref1)
 
                    local pref2IsKnownProject, pref2ProjectCode = isKnownProject(pref2)
 
                    if pref1IsKnownProject
 
                        and mw.language.isKnownLanguageTag(pref2)
 
                        and interwikiTable[pref1ProjectCode].takes_lang_prefix then
 
                        u.project = pref1
 
                        u.lang = pref2
 
                        u.projectCode = pref1ProjectCode
 
                        table.insert(
 
                            trackingCategories,
 
                            '[[Category:UserLinks transclusions with project parameters containing language codes]]'
 
                        )
 
                    elseif pref2IsKnownProject
 
                        and mw.language.isKnownLanguageTag(pref1)
 
                        and interwikiTable[pref2ProjectCode].takes_lang_prefix then
 
                        u.project = pref2
 
                        u.lang = pref1
 
                        u.projectCode = pref2ProjectCode
 
                        table.insert(
 
                            trackingCategories,
 
                            '[[Category:UserLinks transclusions with project parameters containing language codes]]'
 
                        )
 
                    else
 
                        return err(
 
                            '"' .. u.project .. '" is not a valid interwiki prefix',
 
                            'Not a valid interwiki prefix'
 
                        )
 
                    end
 
                else
 
                    return err(
 
                        '"' .. u.project .. '" is not a valid interwiki prefix',
 
                        'Not a valid interwiki prefix'
 
                    )
 
                end
 
            end
 
        end
 
    end
 
   
 
    -- Generate the interwiki prefix. This includes colons.
 
    if u.project or u.lang then
 
        u.interwiki = ''
 
        if u.project then
 
            u.interwiki = u.interwiki .. ':' .. u.project
 
        end
 
        if u.lang then
 
            u.interwiki = u.interwiki .. ':' .. u.lang
 
        end
 
        u.interwiki = u.interwiki .. ':'
 
    else
 
        u.interwiki = ''
 
    end
 
  
    -- Generate the other helper strings.
+
local function makeInvokeFunction(funcName)
    u.usernameHtml = mw.uri.encode(u.username) -- Html-encoded username. Spaces are encoded as pluses.
+
-- Makes a function that can be accessed from #invoke. This is only required
    if u.project then
+
-- for functions that need to access arguments.
        local prefixes = interwikiTable[u.projectCode].iw_prefix
+
return function (frame)
        u.projectLong = prefixes[2] or prefixes[1] -- A bit of a hack, but should find the long prefix name most of the time.
+
mArguments = require('Module:Arguments')
    else
+
local args = mArguments.getArgs(frame)
        u.projectLong = 'wikipedia'
+
return p[funcName](args)
    end
+
end
    u.toolLang = u.lang or 'en' -- set the default language for tools on the toolserver or labs.
 
 
end
 
end
  
local function generateTrackingCategories()
+
p.main = makeInvokeFunction('_main')
    if demo == 'yes' then
+
 
        return ''
+
function p._main(args)
    else
+
-- The main function. This is the one called from [[Template:User-multi]],
        return table.concat(trackingCategories)
+
-- via p.main.
    end
+
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
 
end
  
-- This function generates a table of all available link types, with their previews.
+
function p.getOptions(args)
-- It is used in the module documentation.
+
-- Gets the options from the args table, so that we don't have to pass
local function getLinkTable(args)
+
-- around the whole args table all the time.
    demo = args.demo -- Set the demo variable.
+
local options = {}
    -- Generate the user data strings and return any errors.
+
options.isDemo = yesno(args.demo) or false
    local dataStringError = generateUserDataStrings(args)
+
options.toolbarStyle = yesno(args.small) and 'font-size: 90%;' or nil
    if dataStringError then
+
options.sup = yesno(args.sup, true)
        return dataStringError
+
options.separator = args.separator
    end
+
options.span = args.span
   
+
return options
    -- Build a table of all of the links.
 
    local result = '<table class="wikitable plainlinks sortable">'
 
        .. '\n<tr><th>Code</th><th>Preview</th></tr>'
 
    for i, value in ipairs(linktypes) do
 
        local code = value[1]
 
        result = result .. "\n<tr><td>'''" .. code .. "'''</td><td>" .. getLink(code) .. '</td></tr>'
 
    end
 
    result = result .. '\n</table>'
 
   
 
    return result
 
 
end
 
end
  
local function getSingleLink(args)
+
function p.getCodes(args)
    demo = args.demo -- Set the demo variable.
+
-- Gets the link codes from the arguments. The codes aren't validated
    -- Generate the user data strings and return any errors.
+
-- at this point.
    local dataStringError = generateUserDataStrings(args)
+
mTableTools = maybeLoadModule('Module:TableTools')
    if dataStringError then
+
local codes
        return dataStringError
+
if mTableTools then
    end
+
codes = mTableTools.compressSparseArray(args)
   
+
else
    local linktype = args[1]
+
codes = {}
    if not linktype then  
+
for i, code in ipairs(args) do
        return err('no link type specified')
+
codes[i] = code
    end
+
end
    local result = getLink(linktype)
+
end
    result = result .. generateTrackingCategories()
+
return codes
    return result
 
 
end
 
end
  
local function getLinks(args)
+
function p.export(codes, links, options)
    demo = args.demo -- Set the demo variable.
+
-- Make the user link.
    -- Generate the user data strings and return any errors.
+
local userLink = links.u
    local dataStringError = generateUserDataStrings(args)
+
 
    if dataStringError then
+
-- If we weren't passed any link codes, just return the user link.
        return dataStringError
+
if #codes < 1 then
    end  
+
return userLink
   
+
end
    -- Build the template output.
+
 
    local result = makeToolbar(args) -- Get the toolbar contents.
+
-- Make the toolbar.
    if result then
+
mToolbar = require('Module:Toolbar')
        if args.sup then
+
local toolbarArgs = {}
            result = '<sup>' .. result .. '</sup>'
+
for i, code in ipairs(codes) do
        end
+
local link = links[code]
        result = '&nbsp;' .. result
+
toolbarArgs[#toolbarArgs + 1] = link
    else
+
end
        result = '' -- If there are no links specified, don't return the toolbar at all.
+
toolbarArgs.style = options.toolbarStyle
    end
+
toolbarArgs.separator = options.separator or 'dot'
    result = '<span>' .. makeUserLink() .. result .. '</span>'
+
toolbarArgs.span = options.span
    result = result .. generateTrackingCategories()
+
local toolbar = mToolbar.main(toolbarArgs)
   
+
 
    return result
+
-- Apply the sup option and return the result.
 +
if options.sup then
 +
toolbar = '<sup>' .. toolbar .. '</sup>'
 +
end
 +
return userLink .. '&nbsp;' .. toolbar
 
end
 
end
  
local function getExampleLinks(args)
+
--------------------------------------------------------------------------------
    -- This function enables example output without having to specify any
+
-- Single link function
    -- parameters to #invoke.
+
--------------------------------------------------------------------------------
    args.demo = 'yes'
+
 
    args.user = 'Example'
+
p.single = makeInvokeFunction('_single')
    args.User = nil
+
 
    return getLinks(args)
+
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
 
end
  
local function makeWrapper(func)
+
function p.exportSingle(links, code)
    return function (frame)
+
-- If any errors occur, they will probably occur here. This function
        -- If called via #invoke, use the args passed into the invoking template.
+
-- exists purely so that all the errors that will occur in p._single can
        -- Otherwise, for testing purposes, assume args are being passed directly in.
+
-- be handled using a single pcall.
        local origArgs
+
if not code then
        if frame == mw.getCurrentFrame() then
+
raiseError(
            origArgs = frame:getParent().args
+
message('error-nolinkcode'),
            for k, v in pairs(frame.args) do
+
message('error-nolinkcode-section')
                origArgs = frame.args
+
)
                break
+
end
            end
+
return links[code]
        else
 
            origArgs = frame
 
        end
 
 
        -- Strip whitespace, and treat blank arguments as nil.
 
        -- 'user', 'User', and 'separator' have different behaviour depending on
 
        -- whether they are blank or nil, so keep them as they are.
 
        local args = {}
 
        for k, v in pairs(origArgs) do
 
            v = mw.text.trim(v)
 
            if v ~= '' or k == 'user' or k == 'User' or k == 'separator' then
 
                args[k] = v
 
            end
 
        end
 
   
 
        return func(args)
 
    end
 
 
end
 
end
  
return {
+
--------------------------------------------------------------------------------
    main = makeWrapper(getLinks),
+
-- Link table
    single = makeWrapper(getSingleLink),
+
--------------------------------------------------------------------------------
    linktable = makeWrapper(getLinkTable),
+
 
    example = makeWrapper(getExampleLinks)
+
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

Revision as of 08:25, 5 April 2014

This module generates links about a given user. It is used to generate templates such as {{user}}, {{user5}}, and {{admin}}, usually through its wrapper template {{user-multi}}.

Functions

Main

The main function implements the {{user-multi}} template. It generates a list of links about a given user. Please see the template page for documentation.

Single

The single function generates a single link about a given user. See {{user-multi/link}} for documentation.

Link table

The linktable function generates a wikitext table containing the possible link codes that can be used with the main and single functions, along with example output for each code. It is used on the documentation pages at Template:User-multi/doc and Template:User-multi/link/doc.

It is displayed with the following code:

{{#invoke:UserLinks|linktable}}

Porting to other wikis

If you want to use this module on another wiki, there are a few modules that you must also copy across, and some that can be used but are not essential.

Required modules:

Optional modules:

  • Module:UserLinks/extra - used for testing new link functions before they are moved to the main module.
  • Module:Category handler - if an error occurs, and this module is present, pages are not categorised if they match the module's blacklist.

After you have copied over the necessary modules, you should adjust the configuration settings in Module:UserLinks/config for your language and for your wiki's setup.


--------------------------------------------------------------------------------
--                                 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.
	--
	-- For documentation on how to add new link functions, please see
	-- [[Module:UserLinks#Adding new links]].
	----------------------------------------------------------------------------

	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,
			'Contributions/' .. snippets.username,
			message('display-contributions')
		)
	end

	function linkFunctions.ct(snippets)
		-- Edit count
		return makeUrlLink(
			{
				host = 'tools.wmflabs.org',
				path = '/supercount/index.php',
				query = {
					user = snippets.username,
					project = snippets.toolLang .. '.' .. snippets.projectLong
				}
			},
			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.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 = 'tools.wmflabs.org',
				path = '/xtools/editsummary/index.php',
				query = {
					name = snippets.username,
					lang = snippets.toolLang,
					wiki = snippets.projectLong
				}
			},
			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, user = 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 = '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 and return the result.
	if options.sup then
		toolbar = '<sup>' .. toolbar .. '</sup>'
	end
	return userLink .. '&nbsp;' .. 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