Jump to content

Module:Documentation: Difference between revisions

From Insurer Brain
Content deleted Content added
No edit summary
Tag: Reverted
No edit summary
Tag: Manual revert
 
Line 1: Line 1:
-- This module implements {{pp-meta}} and its daughter templates such as
-- This module implements {{documentation}}.
-- {{pp-dispute}}, {{pp-vandalism}} and {{pp-sock}}.


-- Initialise necessary modules.
-- Get required modules.
require('strict')
local getArgs = require('Module:Arguments').getArgs
local makeFileLink = require('Module:File link')._main
local effectiveProtectionLevel = require('Module:Effective protection level')._main
local effectiveProtectionExpiry = require('Module:Effective protection expiry')._main
local yesno = require('Module:Yesno')


-- Get the config table.
-- Lazily initialise modules and objects we don't always need.
local cfg = mw.loadData('Module:Documentation/config')
local getArgs, makeMessageBox, lang


local p = {}
-- Set constants.
local CONFIG_MODULE = 'Module:Protection banner/config'


-- Often-used functions.
--------------------------------------------------------------------------------
local ugsub = mw.ustring.gsub
local format = mw.ustring.format

----------------------------------------------------------------------------
-- Helper functions
-- Helper functions
--
--------------------------------------------------------------------------------
-- These are defined as local functions, but are made available in the p
-- table for testing purposes.
----------------------------------------------------------------------------


local function makeCategoryLink(cat, sort)
local function message(cfgKey, valArray, expectType)
--[[
if cat then
-- Gets a message from the cfg table and formats it if appropriate.
return string.format(
-- The function raises an error if the value from the cfg table is not
'[[%s:%s|%s]]',
-- of the type expectType. The default type for expectType is 'string'.
mw.site.namespaces[14].name,
-- If the table valArray is present, strings such as $1, $2 etc. in the
cat,
-- message are substituted with values from the table keys [1], [2] etc.
sort
-- For example, if the message "foo-message" had the value 'Foo $2 bar $1.',
)
-- message('foo-message', {'baz', 'qux'}) would return "Foo qux bar baz."
--]]
local msg = cfg[cfgKey]
expectType = expectType or 'string'
if type(msg) ~= expectType then
error('message: type error in message cfg.' .. cfgKey .. ' (' .. expectType .. ' expected, got ' .. type(msg) .. ')', 2)
end
if not valArray then
return msg
end
end
end


local function getMessageVal(match)
-- Validation function for the expiry and the protection date
match = tonumber(match)
local function validateDate(dateString, dateType)
return valArray[match] or error('message: no value found for key $' .. match .. ' in message cfg.' .. cfgKey, 4)
if not lang then
lang = mw.language.getContentLanguage()
end
end
local success, result = pcall(lang.formatDate, lang, 'U', dateString)
if success then
result = tonumber(result)
if result then
return result
end
end
error(string.format(
'invalid %s: %s',
dateType,
tostring(dateString)
), 4)
end


return ugsub(msg, '$([1-9][0-9]*)', getMessageVal)
local function makeFullUrl(page, query, display)
return string.format(
'[%s %s]',
tostring(mw.uri.fullUrl(page, query)),
display
)
end
end


p.message = message
-- Given a directed graph formatted as node -> table of direct successors,

-- get a table of all nodes reachable from a given node (though always
local function makeWikilink(page, display)
-- including the given node).
if display then
local function getReachableNodes(graph, start)
return format('[[%s|%s]]', page, display)
local toWalk, retval = {[start] = true}, {}
else
while true do
return format('[[%s]]', page)
-- Can't use pairs() since we're adding and removing things as we're iterating
local k = next(toWalk) -- This always gets the "first" key
if k == nil then
return retval
end
toWalk[k] = nil
retval[k] = true
for _,v in ipairs(graph[k]) do
if not retval[v] then
toWalk[v] = true
end
end
end
end
end
end


p.makeWikilink = makeWikilink
--------------------------------------------------------------------------------
-- Protection class
--------------------------------------------------------------------------------


local function makeCategoryLink(cat, sort)
local Protection = {}
local catns = mw.site.namespaces[14].name
Protection.__index = Protection
return makeWikilink(catns .. ':' .. cat, sort)
end


p.makeCategoryLink = makeCategoryLink
Protection.supportedActions = {
edit = true,
move = true,
autoreview = true,
upload = true
}


local function makeUrlLink(url, display)
Protection.bannerConfigFields = {
return format('[%s %s]', url, display)
'text',
end
'explanation',
'tooltip',
'alt',
'link',
'image'
}


p.makeUrlLink = makeUrlLink
function Protection.new(args, cfg, title)
local obj = {}
obj._cfg = cfg
obj.title = title or mw.title.getCurrentTitle()


local function makeToolbar(...)
-- Set action
local ret = {}
if not args.action then
local lim = select('#', ...)
obj.action = 'edit'
if lim < 1 then
elseif Protection.supportedActions[args.action] then
return nil
obj.action = args.action
else
error(string.format(
'invalid action: %s',
tostring(args.action)
), 3)
end
end
for i = 1, lim do

ret[#ret + 1] = select(i, ...)
-- Set level
obj.level = args.demolevel or effectiveProtectionLevel(obj.action, obj.title)
if not obj.level or (obj.action == 'move' and obj.level == 'autoconfirmed') then
-- Users need to be autoconfirmed to move pages anyway, so treat
-- semi-move-protected pages as unprotected.
obj.level = '*'
end
end
-- 'documentation-toolbar'
return format(
'<span class="%s">(%s)</span>',
message('toolbar-class'),
table.concat(ret, ' &#124; ')
)
end


p.makeToolbar = makeToolbar
-- Set expiry
local effectiveExpiry = effectiveProtectionExpiry(obj.action, obj.title)
if effectiveExpiry == 'infinity' then
obj.expiry = 'indef'
elseif effectiveExpiry ~= 'unknown' then
obj.expiry = validateDate(effectiveExpiry, 'expiry date')
end


----------------------------------------------------------------------------
-- Set reason
-- Argument processing
if args[1] then
----------------------------------------------------------------------------
obj.reason = mw.ustring.lower(args[1])
if obj.reason:find('|') then
error('reasons cannot contain the pipe character ("|")', 3)
end
end


local function makeInvokeFunc(funcName)
-- Set protection date
return function (frame)
if args.date then
local args = getArgs(frame, {
obj.protectionDate = validateDate(args.date, 'protection date')
valueFunc = function (key, value)
end
if type(value) == 'string' then
value = value:match('^%s*(.-)%s*$') -- Remove whitespace.
-- Set banner config
if key == 'heading' or value ~= '' then
do
return value
obj.bannerConfig = {}
else
local configTables = {}
return nil
if cfg.banners[obj.action] then
end
configTables[#configTables + 1] = cfg.banners[obj.action][obj.reason]
end
else
return value
if cfg.defaultBanners[obj.action] then
configTables[#configTables + 1] = cfg.defaultBanners[obj.action][obj.level]
configTables[#configTables + 1] = cfg.defaultBanners[obj.action].default
end
configTables[#configTables + 1] = cfg.masterBanner
for i, field in ipairs(Protection.bannerConfigFields) do
for j, t in ipairs(configTables) do
if t[field] then
obj.bannerConfig[field] = t[field]
break
end
end
end
end
end
})
return p[funcName](args)
end
end
return setmetatable(obj, Protection)
end
end


----------------------------------------------------------------------------
function Protection:isUserScript()
-- Entry points
-- Whether the page is a user JavaScript or CSS page.
----------------------------------------------------------------------------
local title = self.title
return title.namespace == 2 and (
title.contentModel == 'javascript' or title.contentModel == 'css'
)
end


function Protection:isProtected()
function p.nonexistent(frame)
if mw.title.getCurrentTitle().subpageText == 'testcases' then
return self.level ~= '*'
return frame:expandTemplate{title = 'module test cases notice'}
else
return p.main(frame)
end
end
end


p.main = makeInvokeFunc('_main')
function Protection:shouldShowLock()
-- Whether we should output a banner/padlock
return self:isProtected() and not self:isUserScript()
end


function p._main(args)
-- Whether this page needs a protection category.
--[[
Protection.shouldHaveProtectionCategory = Protection.shouldShowLock
-- This function defines logic flow for the module.

-- @args - table of arguments passed by the user
function Protection:isTemporary()
--]]
return type(self.expiry) == 'number'
local env = p.getEnvironment(args)
local root = mw.html.create()
root
:wikitext(p._getModuleWikitext(args, env))
:wikitext(p.protectionTemplate(env))
:wikitext(p.sandboxNotice(args, env))
:tag('div')
-- 'documentation-container'
:addClass(message('container'))
:attr('role', 'complementary')
:attr('aria-labelledby', args.heading ~= '' and 'documentation-heading' or nil)
:attr('aria-label', args.heading == '' and 'Documentation' or nil)
:newline()
:tag('div')
-- 'documentation'
:addClass(message('main-div-classes'))
:newline()
:wikitext(p._startBox(args, env))
:wikitext(p._content(args, env))
:tag('div')
-- 'documentation-clear'
:addClass(message('clear'))
:done()
:newline()
:done()
:wikitext(p._endBox(args, env))
:done()
:wikitext(p.addTrackingCategories(env))
-- 'Module:Documentation/styles.css'
return mw.getCurrentFrame():extensionTag (
'templatestyles', '', {src=cfg['templatestyles']
}) .. tostring(root)
end
end


----------------------------------------------------------------------------
function Protection:makeProtectionCategory()
-- Environment settings
if not self:shouldHaveProtectionCategory() then
----------------------------------------------------------------------------
return ''
end

local cfg = self._cfg
local title = self.title
-- Get the expiry key fragment.
local expiryFragment
if self.expiry == 'indef' then
expiryFragment = self.expiry
elseif type(self.expiry) == 'number' then
expiryFragment = 'temp'
end

-- Get the namespace key fragment.
local namespaceFragment = cfg.categoryNamespaceKeys[title.namespace]
if not namespaceFragment and title.namespace % 2 == 1 then
namespaceFragment = 'talk'
end

-- Define the order that key fragments are tested in. This is done with an
-- array of tables containing the value to be tested, along with its
-- position in the cfg.protectionCategories table.
local order = {
{val = expiryFragment, keypos = 1},
{val = namespaceFragment, keypos = 2},
{val = self.reason, keypos = 3},
{val = self.level, keypos = 4},
{val = self.action, keypos = 5}
}


function p.getEnvironment(args)
--[[
--[[
-- Returns a table with information about the environment, including title
-- The old protection templates used an ad-hoc protection category system,
-- objects and other namespace- or path-related data.
-- with some templates prioritising namespaces in their categories, and
-- @args - table of arguments passed by the user
-- others prioritising the protection reason. To emulate this in this module
--
-- we use the config table cfg.reasonsWithNamespacePriority to set the
-- Title objects include:
-- reasons for which namespaces have priority over protection reason.
-- env.title - the page we are making documentation for (usually the current title)
-- If we are dealing with one of those reasons, move the namespace table to
-- env.templateTitle - the template (or module, file, etc.)
-- the end of the order table, i.e. give it highest priority. If not, the
-- env.docTitle - the /doc subpage.
-- reason should have highest priority, so move that to the end of the table
-- env.sandboxTitle - the /sandbox subpage.
-- instead.
-- env.testcasesTitle - the /testcases subpage.
--]]
table.insert(order, table.remove(order, self.reason and cfg.reasonsWithNamespacePriority[self.reason] and 2 or 3))
--[[
-- Define the attempt order. Inactive subtables (subtables with nil "value"
-- fields) are moved to the end, where they will later be given the key
-- "all". This is to cut down on the number of table lookups in
-- cfg.protectionCategories, which grows exponentially with the number of
-- non-nil keys. We keep track of the number of active subtables with the
-- noActive parameter.
--]]
local noActive, attemptOrder
do
local active, inactive = {}, {}
for i, t in ipairs(order) do
if t.val then
active[#active + 1] = t
else
inactive[#inactive + 1] = t
end
end
noActive = #active
attemptOrder = active
for i, t in ipairs(inactive) do
attemptOrder[#attemptOrder + 1] = t
end
end
--[[
-- Check increasingly generic key combinations until we find a match. If a
-- specific category exists for the combination of key fragments we are
-- given, that match will be found first. If not, we keep trying different
-- key fragment combinations until we match using the key
-- "all-all-all-all-all".
--
--
-- Data includes:
-- To generate the keys, we index the key subtables using a binary matrix
-- env.protectionLevels - the protection levels table of the title object.
-- with indexes i and j. j is only calculated up to the number of active
-- env.subjectSpace - the number of the title's subject namespace.
-- subtables. For example, if there were three active subtables, the matrix
-- env.docSpace - the number of the namespace the title puts its documentation in.
-- would look like this, with 0 corresponding to the key fragment "all", and
-- env.docpageBase - the text of the base page of the /doc, /sandbox and /testcases pages, with namespace.
-- 1 corresponding to other key fragments.
-- env.compareUrl - URL of the Special:ComparePages page comparing the sandbox with the template.
--
--
-- All table lookups are passed through pcall so that errors are caught. If an error occurs, the value
-- j 1 2 3
-- i
-- returned will be nil.
-- 1 1 1 1
-- 2 0 1 1
-- 3 1 0 1
-- 4 0 0 1
-- 5 1 1 0
-- 6 0 1 0
-- 7 1 0 0
-- 8 0 0 0
--
-- Values of j higher than the number of active subtables are set
-- to the string "all".
--
-- A key for cfg.protectionCategories is constructed for each value of i.
-- The position of the value in the key is determined by the keypos field in
-- each subtable.
--]]
--]]
local cats = cfg.protectionCategories
local env, envFuncs = {}, {}
for i = 1, 2^noActive do

local key = {}
-- Set up the metatable. If triggered we call the corresponding function in the envFuncs table. The value
for j, t in ipairs(attemptOrder) do
-- returned by that function is memoized in the env table so that we don't call any of the functions
if j > noActive then
-- more than once. (Nils won't be memoized.)
key[t.keypos] = 'all'
setmetatable(env, {
else
local quotient = i / 2 ^ (j - 1)
__index = function (t, key)
local envFunc = envFuncs[key]
quotient = math.ceil(quotient)
if quotient % 2 == 1 then
if envFunc then
local success, val = pcall(envFunc)
key[t.keypos] = t.val
else
if success then
key[t.keypos] = 'all'
env[key] = val -- Memoise the value.
return val
end
end
end
end
return nil
end
end
})
key = table.concat(key, '|')

local attempt = cats[key]
function envFuncs.title()
if attempt then
-- The title object for the current page, or a test page passed with args.page.
return makeCategoryLink(attempt, title.text)
local title
local titleArg = args.page
if titleArg then
title = mw.title.new(titleArg)
else
title = mw.title.getCurrentTitle()
end
end
return title
end
end
return ''
end


function Protection:isIncorrect()
function envFuncs.templateTitle()
--[[
local expiry = self.expiry
-- The template (or module, etc.) title object.
return not self:shouldHaveProtectionCategory()
-- Messages:
or type(expiry) == 'number' and expiry < os.time()
-- 'sandbox-subpage' --> 'sandbox'
end
-- 'testcases-subpage' --> 'testcases'
--]]
local subjectSpace = env.subjectSpace
local title = env.title
local subpage = title.subpageText
if subpage == message('sandbox-subpage') or subpage == message('testcases-subpage') or (subpage == message('doc-subpage') and mw.title.getCurrentTitle().namespace == env.docSpace) then
return mw.title.makeTitle(subjectSpace, title.baseText)
else
return mw.title.makeTitle(subjectSpace, title.text)
end
end


function Protection:isTemplateProtectedNonTemplate()
function envFuncs.docTitle()
--[[
local action, namespace = self.action, self.title.namespace
-- Title object of the /doc subpage.
return self.level == 'templateeditor'
-- Messages:
and (
-- 'doc-subpage' --> 'doc'
(action ~= 'edit' and action ~= 'move')
--]]
or (namespace ~= 10 and namespace ~= 828)
local title = env.title
)
local docname = args[1] -- User-specified doc page.
end
local docpage
if docname then
docpage = docname
else
docpage = env.docpageBase .. '/' .. message('doc-subpage')
end
return mw.title.new(docpage)
end
function envFuncs.sandboxTitle()
--[[
-- Title object for the /sandbox subpage.
-- Messages:
-- 'sandbox-subpage' --> 'sandbox'
--]]
return mw.title.new(env.docpageBase .. '/' .. message('sandbox-subpage'))
end
function envFuncs.testcasesTitle()
--[[
-- Title object for the /testcases subpage.
-- Messages:
-- 'testcases-subpage' --> 'testcases'
--]]
return mw.title.new(env.docpageBase .. '/' .. message('testcases-subpage'))
end


function Protection:makeCategoryLinks()
function envFuncs.protectionLevels()
-- The protection levels table of the title object.
local msg = self._cfg.msg
return env.title.protectionLevels
local ret = {self:makeProtectionCategory()}
if self:isIncorrect() then
ret[#ret + 1] = makeCategoryLink(
msg['tracking-category-incorrect'],
self.title.text
)
end
end

if self:isTemplateProtectedNonTemplate() then
function envFuncs.subjectSpace()
ret[#ret + 1] = makeCategoryLink(
-- The subject namespace number.
msg['tracking-category-template'],
return mw.site.namespaces[env.title.namespace].subject.id
self.title.text
)
end
end
return table.concat(ret)
end


function envFuncs.docSpace()
--------------------------------------------------------------------------------
-- The documentation namespace number. For most namespaces this is the
-- Blurb class
-- same as the subject namespace. However, pages in the Article, File,
--------------------------------------------------------------------------------
-- MediaWiki or Category namespaces must have their /doc, /sandbox and
-- /testcases pages in talk space.
local subjectSpace = env.subjectSpace
if subjectSpace == 0 or subjectSpace == 6 or subjectSpace == 8 or subjectSpace == 14 then
return subjectSpace + 1
else
return subjectSpace
end
end


function envFuncs.docpageBase()
local Blurb = {}
-- The base page of the /doc, /sandbox, and /testcases subpages.
Blurb.__index = Blurb
-- For some namespaces this is the talk page, rather than the template page.
local templateTitle = env.templateTitle
local docSpace = env.docSpace
local docSpaceText = mw.site.namespaces[docSpace].name
-- Assemble the link. docSpace is never the main namespace, so we can hardcode the colon.
return docSpaceText .. ':' .. templateTitle.text
end
function envFuncs.compareUrl()
-- Diff link between the sandbox and the main template using [[Special:ComparePages]].
local templateTitle = env.templateTitle
local sandboxTitle = env.sandboxTitle
if templateTitle.exists and sandboxTitle.exists then
local compareUrl = mw.uri.canonicalUrl(
'Special:ComparePages',
{ page1 = templateTitle.prefixedText, page2 = sandboxTitle.prefixedText}
)
return tostring(compareUrl)
else
return nil
end
end


return env
Blurb.bannerTextFields = {
end
text = true,
explanation = true,
tooltip = true,
alt = true,
link = true
}


----------------------------------------------------------------------------
function Blurb.new(protectionObj, args, cfg)
-- Auxiliary templates
return setmetatable({
----------------------------------------------------------------------------
_cfg = cfg,
_protectionObj = protectionObj,
_args = args
}, Blurb)
end


p.getModuleWikitext = makeInvokeFunc('_getModuleWikitext')
-- Private methods --


function Blurb:_formatDate(num)
function p._getModuleWikitext(args, env)
local currentTitle = mw.title.getCurrentTitle()
-- Formats a Unix timestamp into dd Month, YYYY format.
if currentTitle.contentModel ~= 'Scribunto' then return end
lang = lang or mw.language.getContentLanguage()
pcall(require, currentTitle.prefixedText) -- if it fails, we don't care
local success, date = pcall(
local moduleWikitext = package.loaded["Module:Module wikitext"]
lang.formatDate,
if moduleWikitext then
lang,
return moduleWikitext.main()
self._cfg.msg['expiry-date-format'] or 'j F Y',
'@' .. tostring(num)
)
if success then
return date
end
end
end
end


function Blurb:_getExpandedMessage(msgKey)
function p.sandboxNotice(args, env)
--[=[
return self:_substituteParameters(self._cfg.msg[msgKey])
-- Generates a sandbox notice for display above sandbox pages.
end
-- @args - a table of arguments passed by the user

-- @env - environment table containing title objects, etc., generated with p.getEnvironment
function Blurb:_substituteParameters(msg)
--
if not self._params then
-- Messages:
local parameterFuncs = {}
-- 'sandbox-notice-image' --> '[[File:Sandbox.svg|50px|alt=|link=]]'

-- 'sandbox-notice-blurb' --> 'This is the $1 for $2.'
parameterFuncs.CURRENTVERSION = self._makeCurrentVersionParameter
-- 'sandbox-notice-diff-blurb' --> 'This is the $1 for $2 ($3).'
parameterFuncs.EDITREQUEST = self._makeEditRequestParameter
-- 'sandbox-notice-pagetype-template' --> '[[Wikipedia:Template test cases|template sandbox]] page'
parameterFuncs.EXPIRY = self._makeExpiryParameter
-- 'sandbox-notice-pagetype-module' --> '[[Wikipedia:Template test cases|module sandbox]] page'
parameterFuncs.EXPLANATIONBLURB = self._makeExplanationBlurbParameter
-- 'sandbox-notice-pagetype-other' --> 'sandbox page'
parameterFuncs.IMAGELINK = self._makeImageLinkParameter
-- 'sandbox-notice-compare-link-display' --> 'diff'
parameterFuncs.INTROBLURB = self._makeIntroBlurbParameter
-- 'sandbox-notice-testcases-blurb' --> 'See also the companion subpage for $1.'
parameterFuncs.INTROFRAGMENT = self._makeIntroFragmentParameter
-- 'sandbox-notice-testcases-link-display' --> 'test cases'
parameterFuncs.PAGETYPE = self._makePagetypeParameter
-- 'sandbox-category' --> 'Template sandboxes'
parameterFuncs.PROTECTIONBLURB = self._makeProtectionBlurbParameter
-- 'module-sandbox-category' --> 'Module sandboxes'
parameterFuncs.PROTECTIONDATE = self._makeProtectionDateParameter
-- 'other-sandbox-category' --> 'Sandboxes outside of template or module namespace'
parameterFuncs.PROTECTIONLEVEL = self._makeProtectionLevelParameter
--]=]
parameterFuncs.PROTECTIONLOG = self._makeProtectionLogParameter
local title = env.title
parameterFuncs.TALKPAGE = self._makeTalkPageParameter
local sandboxTitle = env.sandboxTitle
parameterFuncs.TOOLTIPBLURB = self._makeTooltipBlurbParameter
local templateTitle = env.templateTitle
parameterFuncs.TOOLTIPFRAGMENT = self._makeTooltipFragmentParameter
local subjectSpace = env.subjectSpace
parameterFuncs.VANDAL = self._makeVandalTemplateParameter
if not (subjectSpace and title and sandboxTitle and templateTitle
and mw.title.equals(title, sandboxTitle)) then
self._params = setmetatable({}, {
return nil
__index = function (t, k)
local param
if parameterFuncs[k] then
param = parameterFuncs[k](self)
end
param = param or ''
t[k] = param
return param
end
})
end
end
-- Build the table of arguments to pass to {{ombox}}. We need just two fields, "image" and "text".
local omargs = {}
msg = msg:gsub('${(%u+)}', self._params)
omargs.image = message('sandbox-notice-image')
return msg
-- Get the text. We start with the opening blurb, which is something like
end
-- "This is the template sandbox for [[Template:Foo]] (diff)."

local text = '__EXPECTUNUSEDTEMPLATE__'
function Blurb:_makeCurrentVersionParameter()
local pagetype, sandboxCat
-- A link to the page history or the move log, depending on the kind of
if subjectSpace == 10 then
-- protection.
pagetype = message('sandbox-notice-pagetype-template')
local pagename = self._protectionObj.title.prefixedText
sandboxCat = message('sandbox-category')
if self._protectionObj.action == 'move' then
elseif subjectSpace == 828 then
-- We need the move log link.
pagetype = message('sandbox-notice-pagetype-module')
return makeFullUrl(
sandboxCat = message('module-sandbox-category')
'Special:Log',
{type = 'move', page = pagename},
self:_getExpandedMessage('current-version-move-display')
)
else
else
pagetype = message('sandbox-notice-pagetype-other')
-- We need the history link.
sandboxCat = message('other-sandbox-category')
return makeFullUrl(
end
pagename,
local templateLink = makeWikilink(templateTitle.prefixedText)
{action = 'history'},
local compareUrl = env.compareUrl
self:_getExpandedMessage('current-version-edit-display')
if compareUrl then
)
local compareDisplay = message('sandbox-notice-compare-link-display')
local compareLink = makeUrlLink(compareUrl, compareDisplay)
text = text .. message('sandbox-notice-diff-blurb', {pagetype, templateLink, compareLink})
else
text = text .. message('sandbox-notice-blurb', {pagetype, templateLink})
end
end
-- Get the test cases page blurb if the page exists. This is something like
end
-- "See also the companion subpage for [[Template:Foo/testcases|test cases]]."

local testcasesTitle = env.testcasesTitle
function Blurb:_makeEditRequestParameter()
if testcasesTitle and testcasesTitle.exists then
local mEditRequest = require('Module:Submit an edit request')
if testcasesTitle.contentModel == "Scribunto" then
local action = self._protectionObj.action
local testcasesLinkDisplay = message('sandbox-notice-testcases-link-display')
local level = self._protectionObj.level
local testcasesRunLinkDisplay = message('sandbox-notice-testcases-run-link-display')
local testcasesLink = makeWikilink(testcasesTitle.prefixedText, testcasesLinkDisplay)
-- Get the edit request type.
local testcasesRunLink = makeWikilink(testcasesTitle.talkPageTitle.prefixedText, testcasesRunLinkDisplay)
local requestType
text = text .. '<br />' .. message('sandbox-notice-testcases-run-blurb', {testcasesLink, testcasesRunLink})
if action == 'edit' then
else
if level == 'autoconfirmed' then
local testcasesLinkDisplay = message('sandbox-notice-testcases-link-display')
requestType = 'semi'
local testcasesLink = makeWikilink(testcasesTitle.prefixedText, testcasesLinkDisplay)
elseif level == 'extendedconfirmed' then
text = text .. '<br />' .. message('sandbox-notice-testcases-blurb', {testcasesLink})
requestType = 'extended'
elseif level == 'templateeditor' then
requestType = 'template'
end
end
end
end
requestType = requestType or 'full'
-- Get the display value.
-- Add the sandbox to the sandbox category.
omargs.text = text .. makeCategoryLink(sandboxCat)
local display = self:_getExpandedMessage('edit-request-display')


-- 'documentation-clear'
return mEditRequest._link{type = requestType, display = display}
return '<div class="' .. message('clear') .. '"></div>'
.. require('Module:Message box').main('ombox', omargs)
end
end


function Blurb:_makeExpiryParameter()
function p.protectionTemplate(env)
-- Generates the padlock icon in the top right.
local expiry = self._protectionObj.expiry
-- @env - environment table containing title objects, etc., generated with p.getEnvironment
if type(expiry) == 'number' then
-- Messages:
return self:_formatDate(expiry)
-- 'protection-template' --> 'pp-template'
-- 'protection-template-args' --> {docusage = 'yes'}
local protectionLevels = env.protectionLevels
if not protectionLevels then
return nil
end
local editProt = protectionLevels.edit and protectionLevels.edit[1]
local moveProt = protectionLevels.move and protectionLevels.move[1]
if editProt then
-- The page is edit-protected.
return require('Module:Protection banner')._main{
message('protection-reason-edit'), small = true
}
elseif moveProt and moveProt ~= 'autoconfirmed' then
-- The page is move-protected but not edit-protected. Exclude move
-- protection with the level "autoconfirmed", as this is equivalent to
-- no move protection at all.
return require('Module:Protection banner')._main{
action = 'move', small = true
}
else
else
return expiry
return nil
end
end
end
end


----------------------------------------------------------------------------
function Blurb:_makeExplanationBlurbParameter()
-- Start box
-- Cover special cases first.
----------------------------------------------------------------------------
if self._protectionObj.title.namespace == 8 then
-- MediaWiki namespace
return self:_getExpandedMessage('explanation-blurb-nounprotect')
end


p.startBox = makeInvokeFunc('_startBox')
-- Get explanation blurb table keys
local action = self._protectionObj.action
local level = self._protectionObj.level
local talkKey = self._protectionObj.title.isTalkPage and 'talk' or 'subject'


function p._startBox(args, env)
-- Find the message in the explanation blurb table and substitute any
--[[
-- parameters.
-- This function generates the start box.
local explanations = self._cfg.explanationBlurbs
-- @args - a table of arguments passed by the user
local msg
-- @env - environment table containing title objects, etc., generated with p.getEnvironment
if explanations[action][level] and explanations[action][level][talkKey] then
--
msg = explanations[action][level][talkKey]
-- The actual work is done by p.makeStartBoxLinksData and p.renderStartBoxLinks which make
elseif explanations[action][level] and explanations[action][level].default then
-- the [view] [edit] [history] [purge] links, and by p.makeStartBoxData and p.renderStartBox
msg = explanations[action][level].default
-- which generate the box HTML.
elseif explanations[action].default and explanations[action].default[talkKey] then
--]]
msg = explanations[action].default[talkKey]
env = env or p.getEnvironment(args)
elseif explanations[action].default and explanations[action].default.default then
local links
msg = explanations[action].default.default
local content = args.content
else
if not content or args[1] then
error(string.format(
-- No need to include the links if the documentation is on the template page itself.
'could not find explanation blurb for action "%s", level "%s" and talk key "%s"',
local linksData = p.makeStartBoxLinksData(args, env)
action,
if linksData then
level,
links = p.renderStartBoxLinks(linksData)
talkKey
), 8)
end
end
end
-- Generate the start box html.
return self:_substituteParameters(msg)
local data = p.makeStartBoxData(args, env, links)
end
if data then

return p.renderStartBox(data)
function Blurb:_makeImageLinkParameter()
local imageLinks = self._cfg.imageLinks
local action = self._protectionObj.action
local level = self._protectionObj.level
local msg
if imageLinks[action][level] then
msg = imageLinks[action][level]
elseif imageLinks[action].default then
msg = imageLinks[action].default
else
else
-- User specified no heading.
msg = imageLinks.edit.default
return nil
end
end
return self:_substituteParameters(msg)
end
end


function Blurb:_makeIntroBlurbParameter()
function p.makeStartBoxLinksData(args, env)
--[[
if self._protectionObj:isTemporary() then
-- Does initial processing of data to make the [view] [edit] [history] [purge] links.
return self:_getExpandedMessage('intro-blurb-expiry')
-- @args - a table of arguments passed by the user
else
-- @env - environment table containing title objects, etc., generated with p.getEnvironment
return self:_getExpandedMessage('intro-blurb-noexpiry')
--
-- Messages:
-- 'view-link-display' --> 'view'
-- 'edit-link-display' --> 'edit'
-- 'history-link-display' --> 'history'
-- 'purge-link-display' --> 'purge'
-- 'module-preload' --> 'Template:Documentation/preload-module-doc'
-- 'docpage-preload' --> 'Template:Documentation/preload'
-- 'create-link-display' --> 'create'
--]]
local subjectSpace = env.subjectSpace
local title = env.title
local docTitle = env.docTitle
if not title or not docTitle then
return nil
end
if docTitle.isRedirect then
docTitle = docTitle.redirectTarget
end
end
end


-- Create link if /doc doesn't exist.
function Blurb:_makeIntroFragmentParameter()
local preload = args.preload
if self._protectionObj:isTemporary() then
if not preload then
return self:_getExpandedMessage('intro-fragment-expiry')
if subjectSpace == 828 then -- Module namespace
else
preload = message('module-preload')
return self:_getExpandedMessage('intro-fragment-noexpiry')
else
preload = message('docpage-preload')
end
end
end
return {
title = title,
docTitle = docTitle,
-- View, display, edit, and purge links if /doc exists.
viewLinkDisplay = message('view-link-display'),
editLinkDisplay = message('edit-link-display'),
historyLinkDisplay = message('history-link-display'),
purgeLinkDisplay = message('purge-link-display'),
preload = preload,
createLinkDisplay = message('create-link-display')
}
end
end


function Blurb:_makePagetypeParameter()
function p.renderStartBoxLinks(data)
--[[
local pagetypes = self._cfg.pagetypes
-- Generates the [view][edit][history][purge] or [create][purge] links from the data table.
return pagetypes[self._protectionObj.title.namespace]
-- @data - a table of data generated by p.makeStartBoxLinksData
or pagetypes.default
--]]
or error('no default pagetype defined', 8)
local docTitle = data.docTitle
end
-- yes, we do intend to purge the template page on which the documentation appears

local purgeLink = makeWikilink("Special:Purge/" .. data.title.prefixedText, data.purgeLinkDisplay)
function Blurb:_makeProtectionBlurbParameter()
local protectionBlurbs = self._cfg.protectionBlurbs
if docTitle.exists then
local action = self._protectionObj.action
local viewLink = makeWikilink(docTitle.prefixedText, data.viewLinkDisplay)
local level = self._protectionObj.level
local editLink = makeWikilink("Special:EditPage/" .. docTitle.prefixedText, data.editLinkDisplay)
local msg
local historyLink = makeWikilink("Special:PageHistory/" .. docTitle.prefixedText, data.historyLinkDisplay)
if protectionBlurbs[action][level] then
return "&#91;" .. viewLink .. "&#93; &#91;" .. editLink .. "&#93; &#91;" .. historyLink .. "&#93; &#91;" .. purgeLink .. "&#93;"
msg = protectionBlurbs[action][level]
elseif protectionBlurbs[action].default then
msg = protectionBlurbs[action].default
elseif protectionBlurbs.edit.default then
msg = protectionBlurbs.edit.default
else
else
local createLink = makeUrlLink(docTitle:canonicalUrl{action = 'edit', preload = data.preload}, data.createLinkDisplay)
error('no protection blurb defined for protectionBlurbs.edit.default', 8)
return "&#91;" .. createLink .. "&#93; &#91;" .. purgeLink .. "&#93;"
end
end
return self:_substituteParameters(msg)
return ret
end
end


function p.makeStartBoxData(args, env, links)
function Blurb:_makeProtectionDateParameter()
--[=[
local protectionDate = self._protectionObj.protectionDate
-- Does initial processing of data to pass to the start-box render function, p.renderStartBox.
if type(protectionDate) == 'number' then
-- @args - a table of arguments passed by the user
return self:_formatDate(protectionDate)
-- @env - environment table containing title objects, etc., generated with p.getEnvironment
-- @links - a string containing the [view][edit][history][purge] links - could be nil if there's an error.
--
-- Messages:
-- 'documentation-icon-wikitext' --> '[[File:Test Template Info-Icon - Version (2).svg|50px|link=|alt=]]'
-- 'template-namespace-heading' --> 'Template documentation'
-- 'module-namespace-heading' --> 'Module documentation'
-- 'file-namespace-heading' --> 'Summary'
-- 'other-namespaces-heading' --> 'Documentation'
-- 'testcases-create-link-display' --> 'create'
--]=]
local subjectSpace = env.subjectSpace
if not subjectSpace then
-- Default to an "other namespaces" namespace, so that we get at least some output
-- if an error occurs.
subjectSpace = 2
end
local data = {}
-- Heading
local heading = args.heading -- Blank values are not removed.
if heading == '' then
-- Don't display the start box if the heading arg is defined but blank.
return nil
end
if heading then
data.heading = heading
elseif subjectSpace == 10 then -- Template namespace
data.heading = message('documentation-icon-wikitext') .. ' ' .. message('template-namespace-heading')
elseif subjectSpace == 828 then -- Module namespace
data.heading = message('documentation-icon-wikitext') .. ' ' .. message('module-namespace-heading')
elseif subjectSpace == 6 then -- File namespace
data.heading = message('file-namespace-heading')
else
else
data.heading = message('other-namespaces-heading')
return protectionDate
end
end
end
-- Heading CSS

local headingStyle = args['heading-style']
function Blurb:_makeProtectionLevelParameter()
if headingStyle then
local protectionLevels = self._cfg.protectionLevels
data.headingStyleText = headingStyle
local action = self._protectionObj.action
local level = self._protectionObj.level
local msg
if protectionLevels[action][level] then
msg = protectionLevels[action][level]
elseif protectionLevels[action].default then
msg = protectionLevels[action].default
elseif protectionLevels.edit.default then
msg = protectionLevels.edit.default
else
else
-- 'documentation-heading'
error('no protection level defined for protectionLevels.edit.default', 8)
data.headingClass = message('main-div-heading-class')
end
end
return self:_substituteParameters(msg)
-- Data for the [view][edit][history][purge] or [create] links.
if links then
-- 'mw-editsection-like plainlinks'
data.linksClass = message('start-box-link-classes')
data.links = links
end
return data
end
end


function Blurb:_makeProtectionLogParameter()
function p.renderStartBox(data)
-- Renders the start box html.
local pagename = self._protectionObj.title.prefixedText
-- @data - a table of data generated by p.makeStartBoxData.
if self._protectionObj.action == 'autoreview' then
local sbox = mw.html.create('div')
-- We need the pending changes log.
sbox
return makeFullUrl(
-- 'documentation-startbox'
'Special:Log',
:addClass(message('start-box-class'))
{type = 'stable', page = pagename},
:newline()
self:_getExpandedMessage('pc-log-display')
)
:tag('span')
:addClass(data.headingClass)
else
:attr('id', 'documentation-heading')
-- We need the protection log.
:cssText(data.headingStyleText)
return makeFullUrl(
:wikitext(data.heading)
'Special:Log',
local links = data.links
{type = 'protect', page = pagename},
if links then
self:_getExpandedMessage('protection-log-display')
sbox:tag('span')
)
:addClass(data.linksClass)
:attr('id', data.linksId)
:wikitext(links)
end
end
return tostring(sbox)
end
end


----------------------------------------------------------------------------
function Blurb:_makeTalkPageParameter()
-- Documentation content
return string.format(
----------------------------------------------------------------------------
'[[%s:%s#%s|%s]]',
mw.site.namespaces[self._protectionObj.title.namespace].talk.name,
self._protectionObj.title.text,
self._args.section or 'top',
self:_getExpandedMessage('talk-page-link-display')
)
end


p.content = makeInvokeFunc('_content')
function Blurb:_makeTooltipBlurbParameter()

if self._protectionObj:isTemporary() then
function p._content(args, env)
return self:_getExpandedMessage('tooltip-blurb-expiry')
-- Displays the documentation contents
else
-- @args - a table of arguments passed by the user
return self:_getExpandedMessage('tooltip-blurb-noexpiry')
-- @env - environment table containing title objects, etc., generated with p.getEnvironment
env = env or p.getEnvironment(args)
local docTitle = env.docTitle
local content = args.content
if not content and docTitle and docTitle.exists then
content = args._content or mw.getCurrentFrame():expandTemplate{title = docTitle.prefixedText}
end
end
-- The line breaks below are necessary so that "=== Headings ===" at the start and end
-- of docs are interpreted correctly.
return '\n' .. (content or '') .. '\n'
end
end


p.contentTitle = makeInvokeFunc('_contentTitle')
function Blurb:_makeTooltipFragmentParameter()

if self._protectionObj:isTemporary() then
function p._contentTitle(args, env)
return self:_getExpandedMessage('tooltip-fragment-expiry')
env = env or p.getEnvironment(args)
local docTitle = env.docTitle
if not args.content and docTitle and docTitle.exists then
return docTitle.prefixedText
else
else
return ''
return self:_getExpandedMessage('tooltip-fragment-noexpiry')
end
end
end
end


----------------------------------------------------------------------------
function Blurb:_makeVandalTemplateParameter()
-- End box
return mw.getCurrentFrame():expandTemplate{
----------------------------------------------------------------------------
title="vandal-m",
args={self._args.user or self._protectionObj.title.baseText}
}
end


p.endBox = makeInvokeFunc('_endBox')
-- Public methods --


function Blurb:makeBannerText(key)
function p._endBox(args, env)
--[=[
-- Validate input.
-- This function generates the end box (also known as the link box).
if not key or not Blurb.bannerTextFields[key] then
-- @args - a table of arguments passed by the user
error(string.format(
-- @env - environment table containing title objects, etc., generated with p.getEnvironment
'"%s" is not a valid banner config field',
--
tostring(key)
--]=]
), 2)
-- Get environment data.
env = env or p.getEnvironment(args)
local subjectSpace = env.subjectSpace
local docTitle = env.docTitle
if not subjectSpace or not docTitle then
return nil
end
end

-- Check whether we should output the end box at all. Add the end
-- Generate the text.
-- box by default if the documentation exists or if we are in the
local msg = self._protectionObj.bannerConfig[key]
-- user, module or template namespaces.
if type(msg) == 'string' then
local linkBox = args['link box']
return self:_substituteParameters(msg)
elseif type(msg) == 'function' then
if linkBox == 'off'
or not (
msg = msg(self._protectionObj, self._args)
docTitle.exists
if type(msg) ~= 'string' then
or subjectSpace == 2
error(string.format(
or subjectSpace == 828
'bad output from banner config function with key "%s"'
or subjectSpace == 10
.. ' (expected string, got %s)',
)
tostring(key),
then
type(msg)
), 4)
return nil
end
return self:_substituteParameters(msg)
end
end
end


-- Assemble the link box.
--------------------------------------------------------------------------------
local text = ''
-- BannerTemplate class
if linkBox then
--------------------------------------------------------------------------------
text = text .. linkBox

local BannerTemplate = {}
BannerTemplate.__index = BannerTemplate

function BannerTemplate.new(protectionObj, cfg)
local obj = {}
obj._cfg = cfg

-- Set the image filename.
local imageFilename = protectionObj.bannerConfig.image
if imageFilename then
obj._imageFilename = imageFilename
else
else
text = text .. (p.makeDocPageBlurb(args, env) or '') -- "This documentation is transcluded from [[Foo]]."
-- If an image filename isn't specified explicitly in the banner config,
if subjectSpace == 2 or subjectSpace == 10 or subjectSpace == 828 then
-- generate it from the protection status and the namespace.
-- We are in the user, template or module namespaces.
local action = protectionObj.action
-- Add sandbox and testcases links.
local level = protectionObj.level
-- "Editors can experiment in this template's sandbox and testcases pages."
local namespace = protectionObj.title.namespace
text = text .. (p.makeExperimentBlurb(args, env) or '') .. '<br />'
local reason = protectionObj.reason
if not args.content and not args[1] then
-- "Please add categories to the /doc subpage."
-- Deal with special cases first.
-- Don't show this message with inline docs or with an explicitly specified doc page,
if (
-- as then it is unclear where to add the categories.
namespace == 10
text = text .. (p.makeCategoriesBlurb(args, env) or '')
or namespace == 828
or reason and obj._cfg.indefImageReasons[reason]
)
and action == 'edit'
and level == 'sysop'
and not protectionObj:isTemporary()
then
-- Fully protected modules and templates get the special red "indef"
-- padlock.
obj._imageFilename = obj._cfg.msg['image-filename-indef']
else
-- Deal with regular protection types.
local images = obj._cfg.images
if images[action] then
if images[action][level] then
obj._imageFilename = images[action][level]
elseif images[action].default then
obj._imageFilename = images[action].default
end
end
end
text = text .. ' ' .. (p.makeSubpagesBlurb(args, env) or '') --"Subpages of this template"
end
end
end
end
return setmetatable(obj, BannerTemplate)
local box = mw.html.create('div')
end
-- 'documentation-metadata'
box:attr('role', 'note')
:addClass(message('end-box-class'))
-- 'plainlinks'
:addClass(message('end-box-plainlinks'))
:wikitext(text)
:done()


return '\n' .. tostring(box)
function BannerTemplate:renderImage()
local filename = self._imageFilename
or self._cfg.msg['image-filename-default']
or 'Transparent.gif'
return makeFileLink{
file = filename,
size = (self.imageWidth or 20) .. 'px',
alt = self._imageAlt,
link = self._imageLink,
caption = self.imageCaption
}
end
end


function p.makeDocPageBlurb(args, env)
--------------------------------------------------------------------------------
--[=[
-- Banner class
-- Makes the blurb "This documentation is transcluded from [[Template:Foo]] (edit, history)".
--------------------------------------------------------------------------------
-- @args - a table of arguments passed by the user

-- @env - environment table containing title objects, etc., generated with p.getEnvironment
local Banner = setmetatable({}, BannerTemplate)
--
Banner.__index = Banner
-- Messages:

-- 'edit-link-display' --> 'edit'
function Banner.new(protectionObj, blurbObj, cfg)
-- 'history-link-display' --> 'history'
local obj = BannerTemplate.new(protectionObj, cfg) -- This doesn't need the blurb.
-- 'transcluded-from-blurb' -->
obj.imageWidth = 40
-- 'The above [[Wikipedia:Template documentation|documentation]]
obj.imageCaption = blurbObj:makeBannerText('alt') -- Large banners use the alt text for the tooltip.
-- is [[Help:Transclusion|transcluded]] from $1.'
obj._reasonText = blurbObj:makeBannerText('text')
-- 'module-preload' --> 'Template:Documentation/preload-module-doc'
obj._explanationText = blurbObj:makeBannerText('explanation')
-- 'create-link-display' --> 'create'
obj._page = protectionObj.title.prefixedText -- Only makes a difference in testing.
-- 'create-module-doc-blurb' -->
return setmetatable(obj, Banner)
-- 'You might want to $1 a documentation page for this [[Wikipedia:Lua|Scribunto module]].'
--]=]
local docTitle = env.docTitle
if not docTitle then
return nil
end
if docTitle.exists then
-- /doc exists; link to it.
local docLink = makeWikilink(docTitle.prefixedText)
local editDisplay = message('edit-link-display')
local editLink = makeWikilink("Special:EditPage/" .. docTitle.prefixedText, editDisplay)
local historyDisplay = message('history-link-display')
local historyLink = makeWikilink("Special:PageHistory/" .. docTitle.prefixedText, historyDisplay)
return message('transcluded-from-blurb', {docLink})
.. ' '
.. makeToolbar(editLink, historyLink)
.. '<br />'
elseif env.subjectSpace == 828 then
-- /doc does not exist; ask to create it.
local createUrl = docTitle:canonicalUrl{action = 'edit', preload = message('module-preload')}
local createDisplay = message('create-link-display')
local createLink = makeUrlLink(createUrl, createDisplay)
return message('create-module-doc-blurb', {createLink})
.. '<br />'
end
end
end


function Banner:__tostring()
function p.makeExperimentBlurb(args, env)
--[[
-- Renders the banner.
-- Renders the text "Editors can experiment in this template's sandbox (edit | diff) and testcases (edit) pages."
makeMessageBox = makeMessageBox or require('Module:Message box').main
-- @args - a table of arguments passed by the user
local reasonText = self._reasonText or error('no reason text set', 2)
-- @env - environment table containing title objects, etc., generated with p.getEnvironment
local explanationText = self._explanationText
--
local mbargs = {
-- Messages:
page = self._page,
-- 'sandbox-link-display' --> 'sandbox'
type = 'protection',
-- 'sandbox-edit-link-display' --> 'edit'
image = self:renderImage(),
-- 'compare-link-display' --> 'diff'
text = string.format(
-- 'module-sandbox-preload' --> 'Template:Documentation/preload-module-sandbox'
"'''%s'''%s",
-- 'template-sandbox-preload' --> 'Template:Documentation/preload-sandbox'
reasonText,
-- 'sandbox-create-link-display' --> 'create'
explanationText and '<br />' .. explanationText or ''
-- 'mirror-edit-summary' --> 'Create sandbox version of $1'
)
-- 'mirror-link-display' --> 'mirror'
}
-- 'mirror-link-preload' --> 'Template:Documentation/mirror'
return makeMessageBox('mbox', mbargs)
-- 'sandbox-link-display' --> 'sandbox'
-- 'testcases-link-display' --> 'testcases'
-- 'testcases-edit-link-display'--> 'edit'
-- 'template-sandbox-preload' --> 'Template:Documentation/preload-sandbox'
-- 'testcases-create-link-display' --> 'create'
-- 'testcases-link-display' --> 'testcases'
-- 'testcases-edit-link-display' --> 'edit'
-- 'module-testcases-preload' --> 'Template:Documentation/preload-module-testcases'
-- 'template-testcases-preload' --> 'Template:Documentation/preload-testcases'
-- 'experiment-blurb-module' --> 'Editors can experiment in this module's $1 and $2 pages.'
-- 'experiment-blurb-template' --> 'Editors can experiment in this template's $1 and $2 pages.'
--]]
local subjectSpace = env.subjectSpace
local templateTitle = env.templateTitle
local sandboxTitle = env.sandboxTitle
local testcasesTitle = env.testcasesTitle
local templatePage = templateTitle.prefixedText
if not subjectSpace or not templateTitle or not sandboxTitle or not testcasesTitle then
return nil
end
-- Make links.
local sandboxLinks, testcasesLinks
if sandboxTitle.exists then
local sandboxPage = sandboxTitle.prefixedText
local sandboxDisplay = message('sandbox-link-display')
local sandboxLink = makeWikilink(sandboxPage, sandboxDisplay)
local sandboxEditDisplay = message('sandbox-edit-link-display')
local sandboxEditLink = makeWikilink("Special:EditPage/" .. sandboxPage, sandboxEditDisplay)
local compareUrl = env.compareUrl
local compareLink
if compareUrl then
local compareDisplay = message('compare-link-display')
compareLink = makeUrlLink(compareUrl, compareDisplay)
end
sandboxLinks = sandboxLink .. ' ' .. makeToolbar(sandboxEditLink, compareLink)
else
local sandboxPreload
if subjectSpace == 828 then
sandboxPreload = message('module-sandbox-preload')
else
sandboxPreload = message('template-sandbox-preload')
end
local sandboxCreateUrl = sandboxTitle:canonicalUrl{action = 'edit', preload = sandboxPreload}
local sandboxCreateDisplay = message('sandbox-create-link-display')
local sandboxCreateLink = makeUrlLink(sandboxCreateUrl, sandboxCreateDisplay)
local mirrorSummary = message('mirror-edit-summary', {makeWikilink(templatePage)})
local mirrorPreload = message('mirror-link-preload')
local mirrorUrl = sandboxTitle:canonicalUrl{action = 'edit', preload = mirrorPreload, summary = mirrorSummary}
if subjectSpace == 828 then
mirrorUrl = sandboxTitle:canonicalUrl{action = 'edit', preload = templateTitle.prefixedText, summary = mirrorSummary}
end
local mirrorDisplay = message('mirror-link-display')
local mirrorLink = makeUrlLink(mirrorUrl, mirrorDisplay)
sandboxLinks = message('sandbox-link-display') .. ' ' .. makeToolbar(sandboxCreateLink, mirrorLink)
end
if testcasesTitle.exists then
local testcasesPage = testcasesTitle.prefixedText
local testcasesDisplay = message('testcases-link-display')
local testcasesLink = makeWikilink(testcasesPage, testcasesDisplay)
local testcasesEditUrl = testcasesTitle:canonicalUrl{action = 'edit'}
local testcasesEditDisplay = message('testcases-edit-link-display')
local testcasesEditLink = makeWikilink("Special:EditPage/" .. testcasesPage, testcasesEditDisplay)
-- for Modules, add testcases run link if exists
if testcasesTitle.contentModel == "Scribunto" and testcasesTitle.talkPageTitle and testcasesTitle.talkPageTitle.exists then
local testcasesRunLinkDisplay = message('testcases-run-link-display')
local testcasesRunLink = makeWikilink(testcasesTitle.talkPageTitle.prefixedText, testcasesRunLinkDisplay)
testcasesLinks = testcasesLink .. ' ' .. makeToolbar(testcasesEditLink, testcasesRunLink)
else
testcasesLinks = testcasesLink .. ' ' .. makeToolbar(testcasesEditLink)
end
else
local testcasesPreload
if subjectSpace == 828 then
testcasesPreload = message('module-testcases-preload')
else
testcasesPreload = message('template-testcases-preload')
end
local testcasesCreateUrl = testcasesTitle:canonicalUrl{action = 'edit', preload = testcasesPreload}
local testcasesCreateDisplay = message('testcases-create-link-display')
local testcasesCreateLink = makeUrlLink(testcasesCreateUrl, testcasesCreateDisplay)
testcasesLinks = message('testcases-link-display') .. ' ' .. makeToolbar(testcasesCreateLink)
end
local messageName
if subjectSpace == 828 then
messageName = 'experiment-blurb-module'
else
messageName = 'experiment-blurb-template'
end
return message(messageName, {sandboxLinks, testcasesLinks})
end
end


function p.makeCategoriesBlurb(args, env)
--------------------------------------------------------------------------------
--[[
-- Padlock class
-- Generates the text "Please add categories to the /doc subpage."
--------------------------------------------------------------------------------
-- @args - a table of arguments passed by the user

-- @env - environment table containing title objects, etc., generated with p.getEnvironment
local Padlock = setmetatable({}, BannerTemplate)
-- Messages:
Padlock.__index = Padlock
-- 'doc-link-display' --> '/doc'

-- 'add-categories-blurb' --> 'Please add categories to the $1 subpage.'
function Padlock.new(protectionObj, blurbObj, cfg)
--]]
local obj = BannerTemplate.new(protectionObj, cfg) -- This doesn't need the blurb.
local docTitle = env.docTitle
obj.imageWidth = 20
if not docTitle then
obj.imageCaption = blurbObj:makeBannerText('tooltip')
return nil
obj._imageAlt = blurbObj:makeBannerText('alt')
end
obj._imageLink = blurbObj:makeBannerText('link')
local docPathLink = makeWikilink(docTitle.prefixedText, message('doc-link-display'))
obj._indicatorName = cfg.padlockIndicatorNames[protectionObj.action]
return message('add-categories-blurb', {docPathLink})
or cfg.padlockIndicatorNames.default
or 'pp-default'
return setmetatable(obj, Padlock)
end
end


function Padlock:__tostring()
function p.makeSubpagesBlurb(args, env)
--[[
local frame = mw.getCurrentFrame()
-- Generates the "Subpages of this template" link.
-- The nowiki tag helps prevent whitespace at the top of articles.
-- @args - a table of arguments passed by the user
return frame:extensionTag{name = 'nowiki'} .. frame:extensionTag{
-- @env - environment table containing title objects, etc., generated with p.getEnvironment
name = 'indicator',
args = {name = self._indicatorName},
content = self:renderImage()
}
end

--------------------------------------------------------------------------------
-- Exports
--------------------------------------------------------------------------------

local p = {}

function p._exportClasses()
-- This is used for testing purposes.
return {
Protection = Protection,
Blurb = Blurb,
BannerTemplate = BannerTemplate,
Banner = Banner,
Padlock = Padlock,
}
end

function p._main(args, cfg, title)
args = args or {}
cfg = cfg or require(CONFIG_MODULE)

local protectionObj = Protection.new(args, cfg, title)

local ret = {}

-- If a page's edit protection is equally or more restrictive than its
-- protection from some other action, then don't bother displaying anything
-- for the other action (except categories).
if not yesno(args.catonly) and (protectionObj.action == 'edit' or
args.demolevel or
not getReachableNodes(
cfg.hierarchy,
protectionObj.level
)[effectiveProtectionLevel('edit', protectionObj.title)])
then
-- Initialise the blurb object
local blurbObj = Blurb.new(protectionObj, args, cfg)
-- Messages:
-- Render the banner
-- 'template-pagetype' --> 'template'
if protectionObj:shouldShowLock() then
-- 'module-pagetype' --> 'module'
ret[#ret + 1] = tostring(
-- 'default-pagetype' --> 'page'
(yesno(args.small) and Padlock or Banner)
-- 'subpages-link-display' --> 'Subpages of this $1'
.new(protectionObj, blurbObj, cfg)
--]]
)
local subjectSpace = env.subjectSpace
end
local templateTitle = env.templateTitle
if not subjectSpace or not templateTitle then
return nil
end
end
local pagetype

if subjectSpace == 10 then
-- Render the categories
pagetype = message('template-pagetype')
if yesno(args.category) ~= false then
elseif subjectSpace == 828 then
ret[#ret + 1] = protectionObj:makeCategoryLinks()
pagetype = message('module-pagetype')
else
pagetype = message('default-pagetype')
end
end
local subpagesLink = makeWikilink(
'Special:PrefixIndex/' .. templateTitle.prefixedText .. '/',
-- For arbitration enforcement, flagging [[WP:PIA]] pages to enable [[Special:AbuseFilter/1339]] to flag edits to them
message('subpages-link-display', {pagetype})
if protectionObj.level == "extendedconfirmed" then
)
if require("Module:TableTools").inArray(protectionObj.title.talkPageTitle.categories, "Wikipedia pages subject to the extended confirmed restriction related to the Arab-Israeli conflict") then
return message('subpages-blurb', {subpagesLink})
ret[#ret + 1] = "<p class='PIA-flag' style='display:none; visibility:hidden;' title='This page is subject to the extended confirmed restriction related to the Arab-Israeli conflict.'></p>"
end
end
return table.concat(ret)
end
end


----------------------------------------------------------------------------
function p.main(frame, cfg)
-- Tracking categories
cfg = cfg or require(CONFIG_MODULE)
----------------------------------------------------------------------------


function p.addTrackingCategories(env)
-- Find default args, if any.
--[[
local parent = frame.getParent and frame:getParent()
-- Check if {{documentation}} is transcluded on a /doc or /testcases page.
local defaultArgs = parent and cfg.wrappers[parent:getTitle():gsub('/sandbox$', '')]
-- @env - environment table containing title objects, etc., generated with p.getEnvironment

-- Find user args, and use the parent frame if we are being called from a
-- Messages:
-- wrapper template.
-- 'display-strange-usage-category' --> true
getArgs = getArgs or require('Module:Arguments').getArgs
-- 'doc-subpage' --> 'doc'
local userArgs = getArgs(frame, {
-- 'testcases-subpage' --> 'testcases'
parentOnly = defaultArgs,
-- 'strange-usage-category' --> 'Wikipedia pages with strange ((documentation)) usage'
frameOnly = not defaultArgs
})
--
-- /testcases pages in the module namespace are not categorised, as they may have

-- {{documentation}} transcluded automatically.
-- Build the args table. User-specified args overwrite default args.
--]]
local args = {}
local title = env.title
for k, v in pairs(defaultArgs or {}) do
local subjectSpace = env.subjectSpace
args[k] = v
if not title or not subjectSpace then
return nil
end
end
local subpage = title.subpageText
for k, v in pairs(userArgs) do
if message('display-strange-usage-category', nil, 'boolean')
args[k] = v
and (
subpage == message('doc-subpage')
or subjectSpace ~= 828 and subpage == message('testcases-subpage')
)
then
return makeCategoryLink(message('strange-usage-category'))
end
end
return p._main(args, cfg)
return ''
end
end



Latest revision as of 10:46, 18 September 2025

This module displays a green box containing documentation for templates, Lua modules, or other pages. The {{documentation}} template invokes it.

Normal usage

For most uses, you should use the {{documentation}} template; please see that template's page for its usage instructions and parameters.

Use in other modules

To use this module from another Lua module, first load it with require:

local documentation = require('Module:Documentation').main

Then you can simply call it using a table of arguments.

documentation{content = 'Some documentation', ['link box'] = 'My custom link box'}

Please refer to the template documentation for usage instructions and a list of parameters.

Porting to other wikis

The module has a configuration file at Module:Documentation/config which is intended to allow easy translation and porting to other wikis. Please see the code comments in the config page for instructions. If you have any questions, or you need a feature which is not currently implemented, please leave a message at Template talk:Documentation to get the attention of a developer.

The messages that need to be customized to display a documentation template/module at the top of module pages are MediaWiki:Scribunto-doc-page-show and MediaWiki:Scribunto-doc-page-does-not-exist.


-- This module implements {{documentation}}.

-- Get required modules.
local getArgs = require('Module:Arguments').getArgs

-- Get the config table.
local cfg = mw.loadData('Module:Documentation/config')

local p = {}

-- Often-used functions.
local ugsub = mw.ustring.gsub
local format = mw.ustring.format

----------------------------------------------------------------------------
-- Helper functions
--
-- These are defined as local functions, but are made available in the p
-- table for testing purposes.
----------------------------------------------------------------------------

local function message(cfgKey, valArray, expectType)
	--[[
	-- Gets a message from the cfg table and formats it if appropriate.
	-- The function raises an error if the value from the cfg table is not
	-- of the type expectType. The default type for expectType is 'string'.
	-- If the table valArray is present, strings such as $1, $2 etc. in the
	-- message are substituted with values from the table keys [1], [2] etc.
	-- For example, if the message "foo-message" had the value 'Foo $2 bar $1.',
	-- message('foo-message', {'baz', 'qux'}) would return "Foo qux bar baz."
	--]]
	local msg = cfg[cfgKey]
	expectType = expectType or 'string'
	if type(msg) ~= expectType then
		error('message: type error in message cfg.' .. cfgKey .. ' (' .. expectType .. ' expected, got ' .. type(msg) .. ')', 2)
	end
	if not valArray then
		return msg
	end

	local function getMessageVal(match)
		match = tonumber(match)
		return valArray[match] or error('message: no value found for key $' .. match .. ' in message cfg.' .. cfgKey, 4)
	end

	return ugsub(msg, '$([1-9][0-9]*)', getMessageVal)
end

p.message = message

local function makeWikilink(page, display)
	if display then
		return format('[[%s|%s]]', page, display)
	else
		return format('[[%s]]', page)
	end
end

p.makeWikilink = makeWikilink

local function makeCategoryLink(cat, sort)
	local catns = mw.site.namespaces[14].name
	return makeWikilink(catns .. ':' .. cat, sort)
end

p.makeCategoryLink = makeCategoryLink

local function makeUrlLink(url, display)
	return format('[%s %s]', url, display)
end

p.makeUrlLink = makeUrlLink

local function makeToolbar(...)
	local ret = {}
	local lim = select('#', ...)
	if lim < 1 then
		return nil
	end
	for i = 1, lim do
		ret[#ret + 1] = select(i, ...)
	end
	-- 'documentation-toolbar'
	return format(
		'<span class="%s">(%s)</span>',
		message('toolbar-class'),
		table.concat(ret, ' &#124; ')
	)
end	

p.makeToolbar = makeToolbar

----------------------------------------------------------------------------
-- Argument processing
----------------------------------------------------------------------------

local function makeInvokeFunc(funcName)
	return function (frame)
		local args = getArgs(frame, {
			valueFunc = function (key, value)
				if type(value) == 'string' then
					value = value:match('^%s*(.-)%s*$') -- Remove whitespace.
					if key == 'heading' or value ~= '' then
						return value
					else
						return nil
					end
				else
					return value
				end
			end
		})
		return p[funcName](args)
	end
end

----------------------------------------------------------------------------
-- Entry points
----------------------------------------------------------------------------

function p.nonexistent(frame)
	if mw.title.getCurrentTitle().subpageText == 'testcases' then
		return frame:expandTemplate{title = 'module test cases notice'}
	else
		return p.main(frame)
	end
end

p.main = makeInvokeFunc('_main')

function p._main(args)
	--[[
	-- This function defines logic flow for the module.
	-- @args - table of arguments passed by the user
	--]]
	local env = p.getEnvironment(args)
	local root = mw.html.create()
	root
		:wikitext(p._getModuleWikitext(args, env))
		:wikitext(p.protectionTemplate(env))
		:wikitext(p.sandboxNotice(args, env))
		:tag('div')
			-- 'documentation-container'
			:addClass(message('container'))
			:attr('role', 'complementary')
			:attr('aria-labelledby', args.heading ~= '' and 'documentation-heading' or nil)
			:attr('aria-label', args.heading == '' and 'Documentation' or nil)
			:newline()
			:tag('div')
				-- 'documentation'
				:addClass(message('main-div-classes'))
				:newline()
				:wikitext(p._startBox(args, env))
				:wikitext(p._content(args, env))
				:tag('div')
					-- 'documentation-clear'
					:addClass(message('clear'))
					:done()
				:newline()
				:done()
			:wikitext(p._endBox(args, env))
			:done()
		:wikitext(p.addTrackingCategories(env))
	-- 'Module:Documentation/styles.css'
	return mw.getCurrentFrame():extensionTag (
		'templatestyles', '', {src=cfg['templatestyles']
	}) .. tostring(root)
end

----------------------------------------------------------------------------
-- Environment settings
----------------------------------------------------------------------------

function p.getEnvironment(args)
	--[[
	-- Returns a table with information about the environment, including title
	-- objects and other namespace- or path-related data.
	-- @args - table of arguments passed by the user
	--
	-- Title objects include:
	-- env.title - the page we are making documentation for (usually the current title)
	-- env.templateTitle - the template (or module, file, etc.)
	-- env.docTitle - the /doc subpage.
	-- env.sandboxTitle - the /sandbox subpage.
	-- env.testcasesTitle - the /testcases subpage.
	--
	-- Data includes:
	-- env.protectionLevels - the protection levels table of the title object.
	-- env.subjectSpace - the number of the title's subject namespace.
	-- env.docSpace - the number of the namespace the title puts its documentation in.
	-- env.docpageBase - the text of the base page of the /doc, /sandbox and /testcases pages, with namespace.
	-- env.compareUrl - URL of the Special:ComparePages page comparing the sandbox with the template.
	-- 
	-- All table lookups are passed through pcall so that errors are caught. If an error occurs, the value
	-- returned will be nil.
	--]]
	
	local env, envFuncs = {}, {}

	-- Set up the metatable. If triggered we call the corresponding function in the envFuncs table. The value
	-- returned by that function is memoized in the env table so that we don't call any of the functions
	-- more than once. (Nils won't be memoized.)
	setmetatable(env, {
		__index = function (t, key)
			local envFunc = envFuncs[key]
			if envFunc then
				local success, val = pcall(envFunc)
				if success then
					env[key] = val -- Memoise the value.
					return val
				end
			end
			return nil
		end
	})	

	function envFuncs.title()
		-- The title object for the current page, or a test page passed with args.page.
		local title
		local titleArg = args.page
		if titleArg then
			title = mw.title.new(titleArg)
		else
			title = mw.title.getCurrentTitle()
		end
		return title
	end

	function envFuncs.templateTitle()
		--[[
		-- The template (or module, etc.) title object.
		-- Messages:
		-- 'sandbox-subpage' --> 'sandbox'
		-- 'testcases-subpage' --> 'testcases'
		--]]
		local subjectSpace = env.subjectSpace
		local title = env.title
		local subpage = title.subpageText
		if subpage == message('sandbox-subpage') or subpage == message('testcases-subpage') or (subpage == message('doc-subpage') and mw.title.getCurrentTitle().namespace == env.docSpace) then
			return mw.title.makeTitle(subjectSpace, title.baseText)
		else
			return mw.title.makeTitle(subjectSpace, title.text)
		end
	end

	function envFuncs.docTitle()
		--[[
		-- Title object of the /doc subpage.
		-- Messages:
		-- 'doc-subpage' --> 'doc'
		--]]
		local title = env.title
		local docname = args[1] -- User-specified doc page.
		local docpage
		if docname then
			docpage = docname
		else
			docpage = env.docpageBase .. '/' .. message('doc-subpage')
		end
		return mw.title.new(docpage)
	end
	
	function envFuncs.sandboxTitle()
		--[[
		-- Title object for the /sandbox subpage.
		-- Messages:
		-- 'sandbox-subpage' --> 'sandbox'
		--]]
		return mw.title.new(env.docpageBase .. '/' .. message('sandbox-subpage'))
	end
	
	function envFuncs.testcasesTitle()
		--[[
		-- Title object for the /testcases subpage.
		-- Messages:
		-- 'testcases-subpage' --> 'testcases'
		--]]
		return mw.title.new(env.docpageBase .. '/' .. message('testcases-subpage'))
	end

	function envFuncs.protectionLevels()
		-- The protection levels table of the title object.
		return env.title.protectionLevels
	end

	function envFuncs.subjectSpace()
		-- The subject namespace number.
		return mw.site.namespaces[env.title.namespace].subject.id
	end

	function envFuncs.docSpace()
		-- The documentation namespace number. For most namespaces this is the
		-- same as the subject namespace. However, pages in the Article, File,
		-- MediaWiki or Category namespaces must have their /doc, /sandbox and
		-- /testcases pages in talk space.
		local subjectSpace = env.subjectSpace
		if subjectSpace == 0 or subjectSpace == 6 or subjectSpace == 8 or subjectSpace == 14 then
			return subjectSpace + 1
		else
			return subjectSpace
		end
	end

	function envFuncs.docpageBase()
		-- The base page of the /doc, /sandbox, and /testcases subpages.
		-- For some namespaces this is the talk page, rather than the template page.
		local templateTitle = env.templateTitle
		local docSpace = env.docSpace
		local docSpaceText = mw.site.namespaces[docSpace].name
		-- Assemble the link. docSpace is never the main namespace, so we can hardcode the colon.
		return docSpaceText .. ':' .. templateTitle.text
	end
	
	function envFuncs.compareUrl()
		-- Diff link between the sandbox and the main template using [[Special:ComparePages]].
		local templateTitle = env.templateTitle
		local sandboxTitle = env.sandboxTitle
		if templateTitle.exists and sandboxTitle.exists then
			local compareUrl = mw.uri.canonicalUrl(
				'Special:ComparePages',
				{ page1 = templateTitle.prefixedText, page2 = sandboxTitle.prefixedText}
			)
			return tostring(compareUrl)
		else
			return nil
		end
	end		

	return env
end	

----------------------------------------------------------------------------
-- Auxiliary templates
----------------------------------------------------------------------------

p.getModuleWikitext = makeInvokeFunc('_getModuleWikitext')

function p._getModuleWikitext(args, env)
	local currentTitle = mw.title.getCurrentTitle()
	if currentTitle.contentModel ~= 'Scribunto' then return end
	pcall(require, currentTitle.prefixedText) -- if it fails, we don't care
	local moduleWikitext =  package.loaded["Module:Module wikitext"]
	if moduleWikitext then
		return moduleWikitext.main()
	end
end

function p.sandboxNotice(args, env)
	--[=[
	-- Generates a sandbox notice for display above sandbox pages.
	-- @args - a table of arguments passed by the user
	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
	-- 
	-- Messages:
	-- 'sandbox-notice-image' --> '[[File:Sandbox.svg|50px|alt=|link=]]'
	-- 'sandbox-notice-blurb' --> 'This is the $1 for $2.'
	-- 'sandbox-notice-diff-blurb' --> 'This is the $1 for $2 ($3).'
	-- 'sandbox-notice-pagetype-template' --> '[[Wikipedia:Template test cases|template sandbox]] page'
	-- 'sandbox-notice-pagetype-module' --> '[[Wikipedia:Template test cases|module sandbox]] page'
	-- 'sandbox-notice-pagetype-other' --> 'sandbox page'
	-- 'sandbox-notice-compare-link-display' --> 'diff'
	-- 'sandbox-notice-testcases-blurb' --> 'See also the companion subpage for $1.'
	-- 'sandbox-notice-testcases-link-display' --> 'test cases'
	-- 'sandbox-category' --> 'Template sandboxes'
	-- 'module-sandbox-category' --> 'Module sandboxes'
	-- 'other-sandbox-category' --> 'Sandboxes outside of template or module namespace'
	--]=]
	local title = env.title
	local sandboxTitle = env.sandboxTitle
	local templateTitle = env.templateTitle
	local subjectSpace = env.subjectSpace
	if not (subjectSpace and title and sandboxTitle and templateTitle
		and mw.title.equals(title, sandboxTitle)) then
		return nil
	end
	-- Build the table of arguments to pass to {{ombox}}. We need just two fields, "image" and "text".
	local omargs = {}
	omargs.image = message('sandbox-notice-image')
	-- Get the text. We start with the opening blurb, which is something like
	-- "This is the template sandbox for [[Template:Foo]] (diff)."
	local text = '__EXPECTUNUSEDTEMPLATE__'
	local pagetype, sandboxCat
	if subjectSpace == 10 then
		pagetype = message('sandbox-notice-pagetype-template')
		sandboxCat = message('sandbox-category')
	elseif subjectSpace == 828 then
		pagetype = message('sandbox-notice-pagetype-module')
		sandboxCat = message('module-sandbox-category')
	else
		pagetype = message('sandbox-notice-pagetype-other')
		sandboxCat = message('other-sandbox-category')
	end
	local templateLink = makeWikilink(templateTitle.prefixedText)
	local compareUrl = env.compareUrl
	if compareUrl then
		local compareDisplay = message('sandbox-notice-compare-link-display')
		local compareLink = makeUrlLink(compareUrl, compareDisplay)
		text = text .. message('sandbox-notice-diff-blurb', {pagetype, templateLink, compareLink})
	else
		text = text .. message('sandbox-notice-blurb', {pagetype, templateLink})
	end
	-- Get the test cases page blurb if the page exists. This is something like
	-- "See also the companion subpage for [[Template:Foo/testcases|test cases]]."
	local testcasesTitle = env.testcasesTitle
	if testcasesTitle and testcasesTitle.exists then
		if testcasesTitle.contentModel == "Scribunto" then
			local testcasesLinkDisplay = message('sandbox-notice-testcases-link-display')
			local testcasesRunLinkDisplay = message('sandbox-notice-testcases-run-link-display')
			local testcasesLink = makeWikilink(testcasesTitle.prefixedText, testcasesLinkDisplay)
			local testcasesRunLink = makeWikilink(testcasesTitle.talkPageTitle.prefixedText, testcasesRunLinkDisplay)
			text = text .. '<br />' .. message('sandbox-notice-testcases-run-blurb', {testcasesLink, testcasesRunLink})
		else
			local testcasesLinkDisplay = message('sandbox-notice-testcases-link-display')
			local testcasesLink = makeWikilink(testcasesTitle.prefixedText, testcasesLinkDisplay)
			text = text .. '<br />' .. message('sandbox-notice-testcases-blurb', {testcasesLink})
		end
	end
	
	-- Add the sandbox to the sandbox category.
	omargs.text = text .. makeCategoryLink(sandboxCat)

	-- 'documentation-clear'
	return '<div class="' .. message('clear') .. '"></div>'
		.. require('Module:Message box').main('ombox', omargs)
end

function p.protectionTemplate(env)
	-- Generates the padlock icon in the top right.
	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
	-- Messages:
	-- 'protection-template' --> 'pp-template'
	-- 'protection-template-args' --> {docusage = 'yes'}
	local protectionLevels = env.protectionLevels
	if not protectionLevels then
		return nil
	end
	local editProt = protectionLevels.edit and protectionLevels.edit[1]
	local moveProt = protectionLevels.move and protectionLevels.move[1]
	if editProt then
		-- The page is edit-protected.
		return require('Module:Protection banner')._main{
			message('protection-reason-edit'), small = true
		}
	elseif moveProt and moveProt ~= 'autoconfirmed' then
		-- The page is move-protected but not edit-protected. Exclude move
		-- protection with the level "autoconfirmed", as this is equivalent to
		-- no move protection at all.
		return require('Module:Protection banner')._main{
			action = 'move', small = true
		}
	else
		return nil
	end
end

----------------------------------------------------------------------------
-- Start box
----------------------------------------------------------------------------

p.startBox = makeInvokeFunc('_startBox')

function p._startBox(args, env)
	--[[
	-- This function generates the start box.
	-- @args - a table of arguments passed by the user
	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
	-- 
	-- The actual work is done by p.makeStartBoxLinksData and p.renderStartBoxLinks which make
	-- the [view] [edit] [history] [purge] links, and by p.makeStartBoxData and p.renderStartBox
	-- which generate the box HTML.
	--]]
	env = env or p.getEnvironment(args)
	local links
	local content = args.content
	if not content or args[1] then
		-- No need to include the links if the documentation is on the template page itself.
		local linksData = p.makeStartBoxLinksData(args, env)
		if linksData then
			links = p.renderStartBoxLinks(linksData)
		end
	end
	-- Generate the start box html.
	local data = p.makeStartBoxData(args, env, links)
	if data then
		return p.renderStartBox(data)
	else
		-- User specified no heading.
		return nil
	end
end

function p.makeStartBoxLinksData(args, env)
	--[[
	-- Does initial processing of data to make the [view] [edit] [history] [purge] links.
	-- @args - a table of arguments passed by the user
	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
	-- 
	-- Messages:
	-- 'view-link-display' --> 'view'
	-- 'edit-link-display' --> 'edit'
	-- 'history-link-display' --> 'history'
	-- 'purge-link-display' --> 'purge'
	-- 'module-preload' --> 'Template:Documentation/preload-module-doc'
	-- 'docpage-preload' --> 'Template:Documentation/preload'
	-- 'create-link-display' --> 'create'
	--]]
	local subjectSpace = env.subjectSpace
	local title = env.title
	local docTitle = env.docTitle
	if not title or not docTitle then
		return nil
	end
	if docTitle.isRedirect then 
		docTitle = docTitle.redirectTarget
	end

	-- Create link if /doc doesn't exist.
	local preload = args.preload
	if not preload then
		if subjectSpace == 828 then -- Module namespace
			preload = message('module-preload')
		else
			preload = message('docpage-preload')
		end
	end
	
	return {
		title = title,
		docTitle = docTitle,
		-- View, display, edit, and purge links if /doc exists.
		viewLinkDisplay = message('view-link-display'),
		editLinkDisplay = message('edit-link-display'),
		historyLinkDisplay = message('history-link-display'),
		purgeLinkDisplay = message('purge-link-display'),
		preload = preload,
		createLinkDisplay = message('create-link-display')
	}
end

function p.renderStartBoxLinks(data)
	--[[
	-- Generates the [view][edit][history][purge] or [create][purge] links from the data table.
	-- @data - a table of data generated by p.makeStartBoxLinksData
	--]]
	local docTitle = data.docTitle
	-- yes, we do intend to purge the template page on which the documentation appears
	local purgeLink = makeWikilink("Special:Purge/" .. data.title.prefixedText, data.purgeLinkDisplay)
	
	if docTitle.exists then
		local viewLink = makeWikilink(docTitle.prefixedText, data.viewLinkDisplay)
		local editLink = makeWikilink("Special:EditPage/" .. docTitle.prefixedText, data.editLinkDisplay)
		local historyLink = makeWikilink("Special:PageHistory/" .. docTitle.prefixedText, data.historyLinkDisplay)
		return "&#91;" .. viewLink .. "&#93; &#91;" .. editLink .. "&#93; &#91;" .. historyLink .. "&#93; &#91;" .. purgeLink .. "&#93;"
	else
		local createLink = makeUrlLink(docTitle:canonicalUrl{action = 'edit', preload = data.preload}, data.createLinkDisplay)
		return  "&#91;" .. createLink .. "&#93; &#91;" .. purgeLink .. "&#93;"
	end
	return ret
end

function p.makeStartBoxData(args, env, links)
	--[=[
	-- Does initial processing of data to pass to the start-box render function, p.renderStartBox.
	-- @args - a table of arguments passed by the user
	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
	-- @links - a string containing the [view][edit][history][purge] links - could be nil if there's an error.
	--
	-- Messages:
	-- 'documentation-icon-wikitext' --> '[[File:Test Template Info-Icon - Version (2).svg|50px|link=|alt=]]'
	-- 'template-namespace-heading' --> 'Template documentation'
	-- 'module-namespace-heading' --> 'Module documentation'
	-- 'file-namespace-heading' --> 'Summary'
	-- 'other-namespaces-heading' --> 'Documentation'
	-- 'testcases-create-link-display' --> 'create'
	--]=]
	local subjectSpace = env.subjectSpace
	if not subjectSpace then
		-- Default to an "other namespaces" namespace, so that we get at least some output
		-- if an error occurs.
		subjectSpace = 2
	end
	local data = {}
	
	-- Heading
	local heading = args.heading -- Blank values are not removed.
	if heading == '' then
		-- Don't display the start box if the heading arg is defined but blank.
		return nil
	end
	if heading then
		data.heading = heading
	elseif subjectSpace == 10 then -- Template namespace
		data.heading = message('documentation-icon-wikitext') .. ' ' .. message('template-namespace-heading')
	elseif subjectSpace == 828 then -- Module namespace
		data.heading = message('documentation-icon-wikitext') .. ' ' .. message('module-namespace-heading')
	elseif subjectSpace == 6 then -- File namespace
		data.heading = message('file-namespace-heading')
	else
		data.heading = message('other-namespaces-heading')
	end
	
	-- Heading CSS
	local headingStyle = args['heading-style']
	if headingStyle then
		data.headingStyleText = headingStyle
	else
		-- 'documentation-heading'
		data.headingClass = message('main-div-heading-class')
	end
	
	-- Data for the [view][edit][history][purge] or [create] links.
	if links then
		-- 'mw-editsection-like plainlinks'
		data.linksClass = message('start-box-link-classes')
		data.links = links
	end
	
	return data
end

function p.renderStartBox(data)
	-- Renders the start box html.
	-- @data - a table of data generated by p.makeStartBoxData.
	local sbox = mw.html.create('div')
	sbox
		-- 'documentation-startbox'
		:addClass(message('start-box-class'))
		:newline()
		:tag('span')
			:addClass(data.headingClass)
			:attr('id', 'documentation-heading')
			:cssText(data.headingStyleText)
			:wikitext(data.heading)
	local links = data.links
	if links then
		sbox:tag('span')
			:addClass(data.linksClass)
			:attr('id', data.linksId)
			:wikitext(links)
	end
	return tostring(sbox)
end

----------------------------------------------------------------------------
-- Documentation content
----------------------------------------------------------------------------

p.content = makeInvokeFunc('_content')

function p._content(args, env)
	-- Displays the documentation contents
	-- @args - a table of arguments passed by the user
	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
	env = env or p.getEnvironment(args)
	local docTitle = env.docTitle
	local content = args.content
	if not content and docTitle and docTitle.exists then
		content = args._content or mw.getCurrentFrame():expandTemplate{title = docTitle.prefixedText}
	end
	-- The line breaks below are necessary so that "=== Headings ===" at the start and end
	-- of docs are interpreted correctly.
	return '\n' .. (content or '') .. '\n' 
end

p.contentTitle = makeInvokeFunc('_contentTitle')

function p._contentTitle(args, env)
	env = env or p.getEnvironment(args)
	local docTitle = env.docTitle
	if not args.content and docTitle and docTitle.exists then
		return docTitle.prefixedText
	else
		return ''
	end
end

----------------------------------------------------------------------------
-- End box
----------------------------------------------------------------------------

p.endBox = makeInvokeFunc('_endBox')

function p._endBox(args, env)
	--[=[
	-- This function generates the end box (also known as the link box).
	-- @args - a table of arguments passed by the user
	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
	-- 
	--]=]
	
	-- Get environment data.
	env = env or p.getEnvironment(args)
	local subjectSpace = env.subjectSpace
	local docTitle = env.docTitle
	if not subjectSpace or not docTitle then
		return nil
	end
		
	-- Check whether we should output the end box at all. Add the end
	-- box by default if the documentation exists or if we are in the
	-- user, module or template namespaces.
	local linkBox = args['link box']
	if linkBox == 'off'
		or not (
			docTitle.exists
			or subjectSpace == 2
			or subjectSpace == 828
			or subjectSpace == 10
		)
	then
		return nil
	end

	-- Assemble the link box.
	local text = ''
	if linkBox then
		text = text .. linkBox
	else
		text = text .. (p.makeDocPageBlurb(args, env) or '') -- "This documentation is transcluded from [[Foo]]." 
		if subjectSpace == 2 or subjectSpace == 10 or subjectSpace == 828 then
			-- We are in the user, template or module namespaces.
			-- Add sandbox and testcases links.
			-- "Editors can experiment in this template's sandbox and testcases pages."
			text = text .. (p.makeExperimentBlurb(args, env) or '') .. '<br />'
			if not args.content and not args[1] then
				-- "Please add categories to the /doc subpage."
				-- Don't show this message with inline docs or with an explicitly specified doc page,
				-- as then it is unclear where to add the categories.
				text = text .. (p.makeCategoriesBlurb(args, env) or '')
			end
			text = text .. ' ' .. (p.makeSubpagesBlurb(args, env) or '') --"Subpages of this template"
		end
	end
	
	local box = mw.html.create('div')
	-- 'documentation-metadata'
	box:attr('role', 'note')
		:addClass(message('end-box-class'))
		-- 'plainlinks'
		:addClass(message('end-box-plainlinks'))
		:wikitext(text)
		:done()

	return '\n' .. tostring(box)
end

function p.makeDocPageBlurb(args, env)
	--[=[
	-- Makes the blurb "This documentation is transcluded from [[Template:Foo]] (edit, history)".
	-- @args - a table of arguments passed by the user
	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
	-- 
	-- Messages:
	-- 'edit-link-display' --> 'edit'
	-- 'history-link-display' --> 'history'
	-- 'transcluded-from-blurb' --> 
	-- 'The above [[Wikipedia:Template documentation|documentation]] 
	-- is [[Help:Transclusion|transcluded]] from $1.'
	-- 'module-preload' --> 'Template:Documentation/preload-module-doc'
	-- 'create-link-display' --> 'create'
	-- 'create-module-doc-blurb' -->
	-- 'You might want to $1 a documentation page for this [[Wikipedia:Lua|Scribunto module]].'
	--]=]
	local docTitle = env.docTitle
	if not docTitle then
		return nil
	end
	if docTitle.exists then
		-- /doc exists; link to it.
		local docLink = makeWikilink(docTitle.prefixedText)
		local editDisplay = message('edit-link-display')
		local editLink = makeWikilink("Special:EditPage/" .. docTitle.prefixedText, editDisplay)
		local historyDisplay = message('history-link-display')
		local historyLink = makeWikilink("Special:PageHistory/" .. docTitle.prefixedText, historyDisplay)
		return message('transcluded-from-blurb', {docLink})
			.. ' '
			.. makeToolbar(editLink, historyLink)
			.. '<br />'
	elseif env.subjectSpace == 828 then
		-- /doc does not exist; ask to create it.
		local createUrl = docTitle:canonicalUrl{action = 'edit', preload = message('module-preload')}
		local createDisplay = message('create-link-display')
		local createLink = makeUrlLink(createUrl, createDisplay)
		return message('create-module-doc-blurb', {createLink})
			.. '<br />'
	end
end

function p.makeExperimentBlurb(args, env)
	--[[
	-- Renders the text "Editors can experiment in this template's sandbox (edit | diff) and testcases (edit) pages."
	-- @args - a table of arguments passed by the user
	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
	-- 
	-- Messages:
	-- 'sandbox-link-display' --> 'sandbox'
	-- 'sandbox-edit-link-display' --> 'edit'
	-- 'compare-link-display' --> 'diff'
	-- 'module-sandbox-preload' --> 'Template:Documentation/preload-module-sandbox'
	-- 'template-sandbox-preload' --> 'Template:Documentation/preload-sandbox'
	-- 'sandbox-create-link-display' --> 'create'
	-- 'mirror-edit-summary' --> 'Create sandbox version of $1'
	-- 'mirror-link-display' --> 'mirror'
	-- 'mirror-link-preload' --> 'Template:Documentation/mirror'
	-- 'sandbox-link-display' --> 'sandbox'
	-- 'testcases-link-display' --> 'testcases'
	-- 'testcases-edit-link-display'--> 'edit'
	-- 'template-sandbox-preload' --> 'Template:Documentation/preload-sandbox'
	-- 'testcases-create-link-display' --> 'create'
	-- 'testcases-link-display' --> 'testcases'
	-- 'testcases-edit-link-display' --> 'edit'
	-- 'module-testcases-preload' --> 'Template:Documentation/preload-module-testcases'
	-- 'template-testcases-preload' --> 'Template:Documentation/preload-testcases'
	-- 'experiment-blurb-module' --> 'Editors can experiment in this module's $1 and $2 pages.'
	-- 'experiment-blurb-template' --> 'Editors can experiment in this template's $1 and $2 pages.'
	--]]
	local subjectSpace = env.subjectSpace
	local templateTitle = env.templateTitle
	local sandboxTitle = env.sandboxTitle
	local testcasesTitle = env.testcasesTitle
	local templatePage = templateTitle.prefixedText
	if not subjectSpace or not templateTitle or not sandboxTitle or not testcasesTitle then
		return nil
	end
	-- Make links.
	local sandboxLinks, testcasesLinks
	if sandboxTitle.exists then
		local sandboxPage = sandboxTitle.prefixedText
		local sandboxDisplay = message('sandbox-link-display')
		local sandboxLink = makeWikilink(sandboxPage, sandboxDisplay)
		local sandboxEditDisplay = message('sandbox-edit-link-display')
		local sandboxEditLink = makeWikilink("Special:EditPage/" .. sandboxPage, sandboxEditDisplay)
		local compareUrl = env.compareUrl
		local compareLink
		if compareUrl then
			local compareDisplay = message('compare-link-display')
			compareLink = makeUrlLink(compareUrl, compareDisplay)
		end
		sandboxLinks = sandboxLink .. ' ' .. makeToolbar(sandboxEditLink, compareLink)
	else
		local sandboxPreload
		if subjectSpace == 828 then
			sandboxPreload = message('module-sandbox-preload')
		else
			sandboxPreload = message('template-sandbox-preload')
		end
		local sandboxCreateUrl = sandboxTitle:canonicalUrl{action = 'edit', preload = sandboxPreload}
		local sandboxCreateDisplay = message('sandbox-create-link-display')
		local sandboxCreateLink = makeUrlLink(sandboxCreateUrl, sandboxCreateDisplay)
		local mirrorSummary = message('mirror-edit-summary', {makeWikilink(templatePage)})
		local mirrorPreload = message('mirror-link-preload')
		local mirrorUrl = sandboxTitle:canonicalUrl{action = 'edit', preload = mirrorPreload, summary = mirrorSummary}
		if subjectSpace == 828 then
			mirrorUrl = sandboxTitle:canonicalUrl{action = 'edit', preload = templateTitle.prefixedText, summary = mirrorSummary}
		end
		local mirrorDisplay = message('mirror-link-display')
		local mirrorLink = makeUrlLink(mirrorUrl, mirrorDisplay)
		sandboxLinks = message('sandbox-link-display') .. ' ' .. makeToolbar(sandboxCreateLink, mirrorLink)
	end
	if testcasesTitle.exists then
		local testcasesPage = testcasesTitle.prefixedText
		local testcasesDisplay = message('testcases-link-display')
		local testcasesLink = makeWikilink(testcasesPage, testcasesDisplay)
		local testcasesEditUrl = testcasesTitle:canonicalUrl{action = 'edit'}
		local testcasesEditDisplay = message('testcases-edit-link-display')
		local testcasesEditLink = makeWikilink("Special:EditPage/" .. testcasesPage, testcasesEditDisplay)
		-- for Modules, add testcases run link if exists
		if testcasesTitle.contentModel == "Scribunto"  and testcasesTitle.talkPageTitle and testcasesTitle.talkPageTitle.exists then
			local testcasesRunLinkDisplay = message('testcases-run-link-display')
			local testcasesRunLink = makeWikilink(testcasesTitle.talkPageTitle.prefixedText, testcasesRunLinkDisplay)
			testcasesLinks = testcasesLink .. ' ' .. makeToolbar(testcasesEditLink, testcasesRunLink)
		else
			testcasesLinks = testcasesLink .. ' ' .. makeToolbar(testcasesEditLink)
		end
	else
		local testcasesPreload
		if subjectSpace == 828 then
			testcasesPreload = message('module-testcases-preload')
		else
			testcasesPreload = message('template-testcases-preload')
		end
		local testcasesCreateUrl = testcasesTitle:canonicalUrl{action = 'edit', preload = testcasesPreload}
		local testcasesCreateDisplay = message('testcases-create-link-display')
		local testcasesCreateLink = makeUrlLink(testcasesCreateUrl, testcasesCreateDisplay)
		testcasesLinks = message('testcases-link-display') .. ' ' .. makeToolbar(testcasesCreateLink)
	end
	local messageName
	if subjectSpace == 828 then
		messageName = 'experiment-blurb-module'
	else
		messageName = 'experiment-blurb-template'
	end
	return message(messageName, {sandboxLinks, testcasesLinks})
end

function p.makeCategoriesBlurb(args, env)
	--[[
	-- Generates the text "Please add categories to the /doc subpage."
	-- @args - a table of arguments passed by the user
	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
	-- Messages:
	-- 'doc-link-display' --> '/doc'
	-- 'add-categories-blurb' --> 'Please add categories to the $1 subpage.'
	--]]
	local docTitle = env.docTitle
	if not docTitle then
		return nil
	end
	local docPathLink = makeWikilink(docTitle.prefixedText, message('doc-link-display'))
	return message('add-categories-blurb', {docPathLink})
end

function p.makeSubpagesBlurb(args, env)
	--[[
	-- Generates the "Subpages of this template" link.
	-- @args - a table of arguments passed by the user
	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
	
	-- Messages:
	-- 'template-pagetype' --> 'template'
	-- 'module-pagetype' --> 'module'
	-- 'default-pagetype' --> 'page'
	-- 'subpages-link-display' --> 'Subpages of this $1'
	--]]
	local subjectSpace = env.subjectSpace
	local templateTitle = env.templateTitle
	if not subjectSpace or not templateTitle then
		return nil
	end
	local pagetype
	if subjectSpace == 10 then
		pagetype = message('template-pagetype')
	elseif subjectSpace == 828 then
		pagetype = message('module-pagetype')
	else
		pagetype = message('default-pagetype')
	end
	local subpagesLink = makeWikilink(
		'Special:PrefixIndex/' .. templateTitle.prefixedText .. '/',
		message('subpages-link-display', {pagetype})
	)
	return message('subpages-blurb', {subpagesLink})
end

----------------------------------------------------------------------------
-- Tracking categories
----------------------------------------------------------------------------

function p.addTrackingCategories(env)
	--[[
	-- Check if {{documentation}} is transcluded on a /doc or /testcases page.
	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
	
	-- Messages:
	-- 'display-strange-usage-category' --> true
	-- 'doc-subpage' --> 'doc'
	-- 'testcases-subpage' --> 'testcases'
	-- 'strange-usage-category' --> 'Wikipedia pages with strange ((documentation)) usage'
	-- 
	-- /testcases pages in the module namespace are not categorised, as they may have
	-- {{documentation}} transcluded automatically.
	--]]
	local title = env.title
	local subjectSpace = env.subjectSpace
	if not title or not subjectSpace then
		return nil
	end
	local subpage = title.subpageText
	if message('display-strange-usage-category', nil, 'boolean')
		and (
			subpage == message('doc-subpage')
			or subjectSpace ~= 828 and subpage == message('testcases-subpage')
		)
	then
		return makeCategoryLink(message('strange-usage-category'))
	end
	return ''
end

return p