Module:List and Module:Protection banner: Difference between pages

From Frontierpedia, the Microsoft Agent encyclopedia
(Difference between pages)
en>Mr. Stradivarius
(split the HTML rendering code out into a separate function, add a tracking category for deprecated parameters, ignore blank/whitespace arguments, and replace getArgNums with the equivalent function in Module:TableTools)
 
en>Mr. Stradivarius
(add some more variables to the reasons text)
 
Line 1: Line 1:
-- This module outputs different kinds of lists. At the moment, bulleted,
-- This module implements {{pp-meta}} and its daughter templates such as
-- unbulleted, horizontal, ordered, and horizontal ordered lists are supported.
-- {{pp-dispute}}, {{pp-vandalism}} and {{pp-sock}}.


local libUtil = require('libraryUtil')
--------------------------------------------------------------------------------
local checkType = libUtil.checkType
-- Configuration
local mTableTools = require('Module:TableTools')
--------------------------------------------------------------------------------


local p = {}
local cfg = {}
 
cfg.reasons = {
-- $1 = "until" or "or until" depending on the expiry
-- $2 = "disputes", with or without a section link
-- $3 = the type of the page, e.g. "article", "template", or "page"
-- $4 = A blurb "it has been protected for x years, x months and x days."
-- $5 = the protection date
-- $6 = Intro blurb, e.g. "This page is currently
--      [[Help:Protection|protected]] from editing"
-- $7 = {{vandal-m|username}} replacement
blp = {
text = '$6 to promote compliance with'
.. '[[Wikipedia:Biographies of living persons'
.. "|Wikipedia's policy on the biographies"
.. ' of living people]]',
tooltip = '$6 to promote compliance with the policy on biographies of'
.. ' living people',
categoryOrder = 'reason',
},
dispute = {
text = '$6 $1 editing $2 have been resolved',
tooltip = 'due to editing disputes',
dispute = true,
categoryOrder = 'reason',
},
office = {
text = 'This $3 is currently under the scrutiny of the'
.. ' [[Wikipedia:Office actions|Wikimedia Foundation Office]]'
.. ' and is protected. $4',
categoryOrder = 'reason',
},
reset = {
text = 'On $5 this article was reduced to a simplified,'
..' "bare bones" version so that it may be completely rewritten to'
.. ' ensure it meets the policies of'
.. ' [[WP:NPOV|Neutral Point of View]] and [[WP:V|Verifiability]].'
.. ' Standard Wikipedia policies will apply to its rewriting—which'
.. ' will eventually be open to all editors—and will be strictly'
.. ' enforced. The article has been placed under a level of'
.. ' semi-protection temporarily during the rebuilding of this'
.. ' article.\n\nAny insertion of material directly from'
.. ' pre-protection revisions of the article will be removed, as'
.. ' will any material added to the article that is not properly'
.. ' sourced. The associated talk page(s) were also cleared on the'
.. " same date.\n\n'''Administrators may not override this action"
.. ' without approval from someone from the [[WP:OFFICE|Office]].'
.. " No editor may remove this notice.'''",
categoryOrder = 'reason',
categoryReason = 'office',
},
sock = {
text = '$6 to prevent [[Wikipedia:Sock puppetry|sock puppets]] of'
.. ' [[Wikipedia:Blocking policy|blocked]] or'
.. ' [[Wikipedia:List of banned users|banned users]]'
.. ' from editing it',
tooltip = '$6 to prevent sock puppets of blocked or banned users from'
.. ' editing it',
categoryOrder = 'reason',
},
usertalk = {
text = '$6 to prevent $7 from using it to make disruptive edits, such'
.. ' as abusing the'
.. ' {{[[Template:unblock|unblock]]}} template',
explanation = 'If you cannot edit this user talk page and you need to'
.. ' make a change or leave a message, you can'
.. ' [[Wikipedia:Requests for page protection'
.. '#Current requests for edits to a protected page'
.. '|request an edit]],'
.. ' [[Wikipedia:Requests for page protection'
.. '#Current requests for reduction in protection level'
.. '|request unprotection]],'
.. ' [[Special:Userlogin|log in]],'
.. ' or [[Special:UserLogin/signup|create an account]].',
categoryReason = 'all',
},
vandalism = {
text = '$6 due to [[Wikipedia:Vandalism|vandalism]]',
tooltip = 'due to vandalism',
categoryOrder = 'namespace',
},
}
 
cfg.categories = {
-- The key strings follow this format:
-- type, level, ns, reason, expiry
['all-all-all-all-all']                  = 'Wikipedia protected pages',
['all-all-all-office-all']              = 'Wikipedia Office-protected pages',
['edit-all-template-all-all']            = 'Wikipedia protected templates',
['edit-autoconfirmed-all-all-all']      = 'Wikipedia semi-protected pages',
['edit-autoconfirmed-all-all-indef']    = 'Wikipedia indefinitely semi-protected pages',
['edit-autoconfirmed-all-blp-all']      = 'Wikipedia indefinitely semi-protected biographies of living people',
['edit-autoconfirmed-all-blp-temp']      = 'Wikipedia temporarily semi-protected biographies of living people',
['edit-autoconfirmed-all-dispute-all']  = 'Wikipedia pages semi-protected due to dispute',
['edit-autoconfirmed-all-sock-all']      = 'Wikipedia pages semi-protected from banned users',
['edit-autoconfirmed-all-vandalism-all'] = 'Wikipedia pages semi-protected against vandalism',
['edit-autoconfirmed-category-all-all']  = 'Wikipedia semi-protected categories',
['edit-autoconfirmed-file-all-all']      = 'Semi-protected images',
['edit-autoconfirmed-portal-all-all']    = 'Semi-protected portals',
['edit-autoconfirmed-project-all-all']  = 'Semi-protected project pages',
['edit-autoconfirmed-talk-all-all']      = 'Semi-protected talk pages',
['edit-autoconfirmed-template-all-all']  = 'Wikipedia semi-protected templates',
['edit-autoconfirmed-template-all-all']  = 'Wikipedia semi-protected templates',
['edit-autoconfirmed-user-all-all']      = 'Wikipedia semi-protected user and user talk pages',
['edit-sysop-all-blp-all']              = 'Wikipedia indefinitely protected biographies of living people',
['edit-sysop-all-blp-temp']              = 'Wikipedia temporarily protected biographies of living people',
['edit-sysop-all-dispute-all']          = 'Wikipedia pages protected due to dispute',
['edit-sysop-all-sock-all']              = 'Wikipedia pages protected from banned users',
['edit-sysop-all-vandalism-all']        = 'Wikipedia pages protected against vandalism',
['edit-sysop-category-all-all']          = 'Wikipedia protected categories',
['edit-sysop-file-all-all']              = 'Protected images',
['edit-sysop-project-all-all']          = 'Protected project pages',
['edit-sysop-talk-all-all']              = 'Protected talk pages',
['edit-sysop-template-all-all']          = 'Wikipedia protected templates',
['edit-sysop-user-all-all']              = 'Wikipedia protected user and user talk pages',
['move-sysop-all-all-all']              = 'Wikipedia move-protected pages',
['move-sysop-all-all-indef']            = 'Wikipedia indefinitely move-protected pages',
['move-sysop-all-dispute-all']          = 'Wikipedia pages move-protected due to dispute',
['move-sysop-all-vandalism-all']        = 'Wikipedia pages move-protected due to vandalism',
['move-sysop-portal-all-all']            = 'Wikipedia move-protected portals',
['move-sysop-portal-all-all']            = 'Wikipedia move-protected portals',
['move-sysop-project-all-all']          = 'Wikipedia move-protected project pages',
['move-sysop-talk-all-all']              = 'Wikipedia move-protected talk pages',
['move-sysop-template-all-all']          = 'Wikipedia move-protected templates',
['move-sysop-user-all-all']              = 'Wikipedia move-protected user and user talk pages',
['autoreview-autoconfirmed-all-all-all']        = 'Wikipedia pending changes protected pages (level 1)',
['autoreview-reviewer-all-all-all']              = 'Wikipedia pending changes protected pages (level 2)',
}
 
cfg.categoryNamespaces = {
[2] = 'user',
[3] = 'user',
[4] = 'project',
[6] = 'file',
[10] = 'template',
[12] = 'project',
[14] = 'category',
[100] = 'portal',
}
 
cfg.pagetypeNamespaces = {
[0] = 'article',
[6] = 'file',
[10] = 'template',
[14] = 'category',
[828] = 'module',
default = 'page'
}


local listTypes = {
--[[
['bulleted'] = true,
-- Not currently used
['unbulleted'] = true,
local error_categories = {
['horizontal'] = true,
incorrect = 'Wikipedia pages with incorrect protection templates',
['ordered'] = true,
no_expiry = 'Wikipedia protected pages without expiry',
['horizontal_ordered'] = true
create = 'Wikipedia pages tagged as create-protected',
template = 'Wikipedia template-protected pages other than templates and modules'
}
}
--]]
--------------------------------------------------------------------------------
-- Main functions
--------------------------------------------------------------------------------
-- Initialise necessary modules.
local mArguments = require('Module:Arguments')
local mProtectionLevel = require('Module:Effective protection level')._main
local mMessageBox -- only needs to be loaded if we are outputting a banner, so lazily initialise


function p.makeListData(listType, args)
-- Define often-used functions as local variables.
-- Constructs a data table to be passed to p.renderList.
local tconcat = table.concat
local data = {}
local tinsert = table.insert
local tremove = table.remove
local ceil = math.ceil
local format = string.format


-- Classes
local function toTableEnd(t, pos)
data.classes = {}
-- Sends the value at position pos to the end of array t, and shifts the
if listType == 'horizontal' or listType == 'horizontal_ordered' then
-- other items down accordingly.
table.insert(data.classes, 'hlist')
return tinsert(t, tremove(t, pos))
elseif listType == 'unbulleted' then
end
table.insert(data.classes, 'plainlist')
end
table.insert(data.classes, args.class)


-- Main div style
local p = {}
data.style = args.style


-- Indent for horizontal lists
function p.main(frame)
if listType == 'horizontal' or listType == 'horizontal_ordered' then
local args = mArguments.getArgs(frame)
local indent = tonumber(args.indent)
return p._main(args)
indent = indent and indent * 1.6 or 0
end
if indent > 0 then
data.marginLeft = indent .. 'em'
end
end
-- List style types for ordered lists
-- This could be "1, 2, 3", "a, b, c", or a number of others. The list style
-- type is either set by the "type" attribute or the "list-style-type" CSS
-- property.
if listType == 'ordered' or listType == 'horizontal_ordered' then
data.listStyleType = args.list_style_type or args['list-style-type']
data.type = args['type']


-- Detect invalid type attributes and attempt to convert them to
function p._main(args)
-- list-style-type CSS properties.
local title
if data.type
if args.page then
and not data.listStyleType
title = mw.title.new(args.page)
and not tostring(data.type):find('^%s*[1AaIi]%s*$')
then
data.listStyleType = data.type
data.type = nil
end
end
-- List tag type
if listType == 'ordered' or listType == 'horizontal_ordered' then
data.listTag = 'ol'
else
else
data.listTag = 'ul'
title = mw.title.getCurrentTitle()
end
end
local protectionData = p.getProtectionData(title)
end


-- Start number for ordered lists
function p.getProtectionData(title)
data.start = args.start
-- Gets a table containing protection data for the given title. The data
if listType == 'horizontal_ordered' then
-- is cached using a metatable, and so can be indexed as needed without
-- Apply fix to get start numbers working with horizontal ordered lists.
-- a performance loss.
local startNum = tonumber(data.start)
local protectionData = {}
if startNum then
local actions = {
data.counterReset = 'listitem ' .. tostring(startNum - 1)
create = true,
edit = true,
move = true,
autoreview = true
}
setmetatable(protectionData, {
__index = function (t, key)
local level
if actions[key] then
level = mProtectionLevel(key, title)
if level == 'accountcreator' then
-- Lump titleblacklisted pages in with template-protected pages,
-- since templateeditors can do both.
level = 'templateeditor'
end
end
protectionData[key] = level
return level
end
end
end
})
 
return protectionData
-- List style
end
-- ul_style and ol_style are included for backwards compatibility. No
-- distinction is made for ordered or unordered lists.
data.listStyle = args.list_style or args.ul_style or args.ol_style


-- List items
function p.getPagetype(ns)
-- li_style is included for backwards compatibility. item_style was included
-- Returns a string with the page's type. Takes a namespace number as input.
-- to be easier to understand for non-coders.
local pagetype = pagetypeNamespaces[ns] or pagetypeNamespaces.default
data.itemStyle = args.item_style or args.li_style
if not pagetype then
data.items = {}
error('the page type could not be found; please define a name for the key "default"')
for i, num in ipairs(mTableTools.numKeys(args)) do
local item = {}
item.content = args[num]
item.style = args['item_style' .. tostring(num)]
or args['li_style' .. tostring(num)]
item.value = args['item_value' .. tostring(num)]
table.insert(data.items, item)
end
end
return pagetype
return data
end
end


function p.renderList(data)
function p.matchNamespace(ns)
-- Renders the list HTML.
-- Matches a namespace number to a string that can be passed to the
-- namespace parameter of p.getCategoryName.
-- Return the blank string if there are no list items.
if not ns or type(ns) ~= 'number' then
if type(data.items) ~= 'table' or #data.items < 1 then
return nil
return ''
end
end
local nskey = cfg.categoryNamespaces[ns]
-- Render the main div tag.
if not nskey and ns % 2 == 1 then
local root = mw.html.create('div')
nskey = 'talk'
for i, class in ipairs(data.classes or {}) do
root:addClass(class)
end
root:css{['margin-left'] = data.marginLeft}
if data.style then
root:cssText(data.style)
end
end
return nskey
end
function p.getCategoryName(cats, action, level, namespace, reason, expiry)
--[[
-- Gets a category name from the category table, given a combination of
-- the protection type, the protection level, the namespace number, the
-- reason for protection, and the expiry date.
--]]
cats = cats or cfg.categories


-- Render the list tag.
--[[
local list = root:tag(data.listTag or 'ul')
-- Define the properties table. Each property is a table containing the
list
-- canonical order that the property is tested in, the position the
:attr{start = data.start, type = data.type}
-- property has in the category key strings, and the property value itself.
:css{
--]]
['counter-reset'] = data.counterReset,
local properties = {
['list-style-type'] = data.listStyleType
expiry = {order = 1, keypos = 5, val = expiry},
}
namespace = {order = 2, keypos = 3, val = p.matchNamespace(namespace)},
if data.listStyle then
reason = {order = 3, keypos = 4, val = reason},
list:cssText(data.listStyle)
level = {order = 4, keypos = 2, val = level},
end
action = {order = 5, keypos = 1, val = action}
}


-- Render the list items
--[[
for i, t in ipairs(data.items or {}) do
-- Load the category order configuration for the reason specified.
local item = list:tag('li')
-- The configuration is stored in the categoryOrder field of each reason
if data.itemStyle then
-- subtable of cfg.reasons. If the value is a table, then the order is the
item:cssText(data.itemStyle)
-- values specified in the table. If the value is a string, then the
-- property corresponding to that string is tested last (i.e. it is the most
-- important, because it keeps its specified value the longest) and the
-- other properties are tested in the canonical order. If the value is of
-- any other type then the canonical order is used.
--]]
local reasonTable = reason and cfg.reasons[reason]
local categoryOrder = reasonTable and reasonTable.categoryOrder
local categoryOrderType = type(categoryOrder)
local configOrder = {}
if categoryOrderType == 'table' then
local dupes = {}
for i = 1, 5 do
local propertiesKey = categoryOrder[i]
if not propertiesKey then
local msg = 'no entry found for key '
.. i
.. ' in the cfg.reasons.'
.. reason
.. '.categoryOrder table'
error(msg)
end
local property = properties[propertiesKey]
if not property then
local msg = 'invalid value "'
.. propertiesKey
.. '" detected in the cfg.reasons.'
.. reason
.. '.categoryOrder table'
error(msg)
end
if dupes[propertiesKey] then
local msg = 'duplicate values "'
.. propertiesKey
.. '" detected in the cfg.reasons.'
.. reason
.. '.categoryOrder table'
error(msg)
else
dupes[propertiesKey] = true
end
configOrder[i] = property
end
end
if t.style then
else
item:cssText(t.style)
for propertiesKey, t in pairs(properties) do
configOrder[t.order] = t
end
if categoryOrderType == 'string' then
local property = properties[categoryOrder]
if not property then
local msg = '"'
.. categoryOrder
.. '" is not a valid value of cfg.reasons.'
.. reason
.. '.categoryOrder'
error(msg)
end
toTableEnd(configOrder, property.order)
end
end
item
:attr{value = t.value}
:wikitext(t.content)
end
end


return tostring(root)
--[[
end
-- Define the attempt order. Properties with no value defined are moved
 
-- to the end, where they will later be given the value "all". This is
function p.renderTrackingCategories(args)
-- to cut down on the number of table lookups in the cats table, which
local isDeprecated = false -- Tracks deprecated parameters.
-- grows exponentially with the number of properties with valid values.
for i, param in ipairs{'ul_style', 'ol_style', 'li_style'} do
-- We keep track of the number of active properties with the noActive
if args[param] then
-- parameter.
isDeprecated = true
--]]
break
local active, inactive = {}, {}
for i, t in ipairs(configOrder) do
if t.val then
active[#active + 1] = t
else
inactive[#inactive + 1] = t
end
end
end
end
if not isDeprecated then
local noActive = #active
for k, v in pairs(args) do
local attemptOrder = active
k = tostring(k)
for i, t in ipairs(inactive) do
if k:find('^item_style%d+$') or k:find('^li_style%d+$') then
attemptOrder[#attemptOrder + 1] = t
isDeprecated = true
end
break
 
--[[
-- Check increasingly generic key combinations until we find a match.
-- If a specific category exists for the combination of properties
-- we are given, that match will be found first. If not, we keep
-- trying different key combinations until we match using the key
-- "all-all-all-all-all".
--
-- To generate the keys, we index the property subtables using a
-- binary matrix with indexes i and j. j is only calculated up to
-- the number of active properties. For example, if there were three
-- active properties, the matrix would look like this, with 0
-- corresponding to the string "all", and 1 corresponding to the
-- val field in the property table:
--
--  j 1  2  3
-- i 
-- 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 properties are set
-- to the string "all".
--
-- A key for the category table is constructed for each value of i.
-- The correct position of the value in the key is determined by the
-- pos field in the property table.
--]]
for i = 1, 2^noActive do
local key = {}
for j, t in ipairs(attemptOrder) do
if j > noActive then
key[t.keypos] = 'all'
else
local quotient = i / 2 ^ (j - 1)
quotient = ceil(quotient)
if quotient % 2 == 1 then
key[t.keypos] = t.val
else
key[t.keypos] = 'all'
end
end
end
end
key = tconcat(key, '-')
local attempt = cats[key]
if attempt then
return attempt
end
end
end
end
local ret = ''
error(
if isDeprecated then
'No category match found;'
ret = ret .. '[[Category:List templates with deprecated parameters]]'
.. ' please define the category for key "all-all-all-all-all"'
)
end
 
function p.renderImageLink(image, size, link, text, alt)
--[[
-- Renders the image link wikitext All parameters are optional
-- apart from the display text.
--
-- @parameters:
-- image - the image name
-- size - the image size, as a number
-- link - page linked to by the image
-- text - the tooltip text
-- alt - the alt text
--
-- All parameters are optional apart from the text parameter.
--]]
image = image or 'Transparent.gif'
size = size or 20
if link then
link = '|link=' .. link
else
link = ''
end
end
return ret
text = text or error('No text parameter supplied to p.renderImageLink')
if alt then
alt = '|alt=' .. alt
else
alt = ''
end
return string.format('[[Image:%s|%dpx%s|%s%s]]', image, size, link, text, alt)
end
end


function p.makeList(listType, args)
function p.renderPadlock(image, right)
if not listType or not listTypes[listType] then
--[[
error(string.format(
-- Renders the html of the padlock seen in the top-right-hand corner
"bad argument #1 to 'makeList' ('%s' is not a valid list type)",
-- of protected pages.
tostring(listType)
--
), 2)
-- @parameters:
end
-- image - the image wikitext (required)
checkType('makeList', 2, args, 'table')
-- right - the "right" css property value, as a string (optional)
local data = p.makeListData(listType, args)
--]]
local list = p.renderList(data)
image = image or error('No image parameter specified in p.renderPadlock')
local trackingCategories = p.renderTrackingCategories(args)
local root = mw.html.create('div')
return list .. trackingCategories
root
:addClass('metadata topicon nopopups')
:attr('id', 'protected-icon')
:css{display = 'none', right = right or '55px'}
:wikitext(image)
return tostring(root)
end
end


for listType in pairs(listTypes) do
function p.renderBanner(page, image, text)
p[listType] = function (frame)
--[[
local mArguments = require('Module:Arguments')
-- Renders the large protection banner placed at the top of articles,
local origArgs = mArguments.getArgs(frame)
-- using the data provided in the data table.
-- Copy all the arguments to a new table, for faster indexing.
--
local args = {}
-- @parameters:
for k, v in pairs(origArgs) do
-- page - demo page parameter to pass to {{mbox}}
args[k] = v
-- image - the image wikitext
end
-- text - the text to display
return p.makeList(listType, args)
--
end
-- All parameters are optional.
--]]
mMessageBox = require('Module:Message box')
local mbargs = { -- arguments for the message box module
page = page,
type = 'protection',
image = image,
text = text
}
return mMessageBox.main('mbox', mbargs)
end
end
--[[
When to add "Category:Wikipedia pages with incorrect protection templates":
* If it uses a type which is incompatible with the actual protection level of a page, or
* If an expiry date is set and is in the past
When to add "Category:Wikipedia protected pages without expiry":
* No expiry is set, and
* Expiry can be set, and
* The protection type is not "move" (could change this?), and
* The reason doesn't have "indefinitely protected" categories (pp-blp, pp-semi-indef and pp-move-indef have these), and
* The reason is not the generic pp-protected (could change this?)
(This leaves reasons "dispute", "vandalism", "usertalk", and "sock")
]]


return p
return p

Revision as of 05:57, 27 May 2014

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

-- This module implements {{pp-meta}} and its daughter templates such as
-- {{pp-dispute}}, {{pp-vandalism}} and {{pp-sock}}.

--------------------------------------------------------------------------------
-- Configuration
--------------------------------------------------------------------------------

local cfg = {}

cfg.reasons = {
	-- $1 = "until" or "or until" depending on the expiry
	-- $2 = "disputes", with or without a section link
	-- $3 = the type of the page, e.g. "article", "template", or "page"
	-- $4 = A blurb "it has been protected for x years, x months and x days."
	-- $5 = the protection date
	-- $6 = Intro blurb, e.g. "This page is currently
	--      [[Help:Protection|protected]] from editing"
	-- $7 = {{vandal-m|username}} replacement
	blp = {
		text = '$6 to promote compliance with'
			.. '[[Wikipedia:Biographies of living persons'
			.. "|Wikipedia's&nbsp;policy on&nbsp;the&nbsp;biographies"
			.. ' of&nbsp;living&nbsp;people]]',
		tooltip = '$6 to promote compliance with the policy on biographies of'
			.. ' living people',
		categoryOrder = 'reason',
	},
	dispute = {
		text = '$6 $1 editing $2 have been resolved',
		tooltip = 'due to editing disputes',
		dispute = true,
		categoryOrder = 'reason',
	},
	office = {
		text = 'This $3 is currently under the scrutiny of the'
			.. ' [[Wikipedia:Office actions|Wikimedia Foundation Office]]'
			.. ' and is protected. $4',
		categoryOrder = 'reason',
	},
	reset = {
		text = 'On $5 this article was reduced to a simplified,'
			..' "bare bones" version so that it may be completely rewritten to'
			.. ' ensure it meets the policies of'
			.. ' [[WP:NPOV|Neutral Point of View]] and [[WP:V|Verifiability]].'
			.. ' Standard Wikipedia policies will apply to its rewriting—which'
			.. ' will eventually be open to all editors—and will be strictly'
			.. ' enforced. The article has been placed under a level of'
			.. ' semi-protection temporarily during the rebuilding of this'
			.. ' article.\n\nAny insertion of material directly from'
			.. ' pre-protection revisions of the article will be removed, as'
			.. ' will any material added to the article that is not properly'
			.. ' sourced. The associated talk page(s) were also cleared on the'
			.. " same date.\n\n'''Administrators may not override this action"
			.. ' without approval from someone from the [[WP:OFFICE|Office]].'
			.. " No editor may remove this notice.'''",
		categoryOrder = 'reason',
		categoryReason = 'office',
	},
	sock = {
		text = '$6 to prevent [[Wikipedia:Sock puppetry|sock puppets]] of'
			.. ' [[Wikipedia:Blocking policy|blocked]] or'
			.. ' [[Wikipedia:List of banned users|banned users]]'
			.. ' from editing it',
		tooltip = '$6 to prevent sock puppets of blocked or banned users from'
			.. ' editing it',
		categoryOrder = 'reason',
	},
	usertalk = {
		text = '$6 to prevent $7 from using it to make disruptive edits, such'
			.. ' as abusing the'
			.. ' &#123;&#123;[[Template:unblock|unblock]]&#125;&#125; template',
		explanation = 'If you cannot edit this user talk page and you need to'
			.. ' make a change or leave a message, you can'
			.. ' [[Wikipedia:Requests for page protection'
			.. '#Current requests for edits to a protected page'
			.. '|request an edit]],'
			.. ' [[Wikipedia:Requests for page protection'
			.. '#Current requests for reduction in protection level'
			.. '|request unprotection]],'
			.. ' [[Special:Userlogin|log in]],'
			.. ' or [[Special:UserLogin/signup|create an account]].',
		categoryReason = 'all',
	},
	vandalism = {
		text = '$6 due to [[Wikipedia:Vandalism|vandalism]]',
		tooltip = 'due to vandalism',
		categoryOrder = 'namespace',
	},
}

cfg.categories = {
	-- The key strings follow this format:
	-- type, level, ns, reason, expiry
	['all-all-all-all-all']                  = 'Wikipedia protected pages',
	['all-all-all-office-all']               = 'Wikipedia Office-protected pages',
	['edit-all-template-all-all']            = 'Wikipedia protected templates',
	['edit-autoconfirmed-all-all-all']       = 'Wikipedia semi-protected pages',
	['edit-autoconfirmed-all-all-indef']     = 'Wikipedia indefinitely semi-protected pages',
	['edit-autoconfirmed-all-blp-all']       = 'Wikipedia indefinitely semi-protected biographies of living people',
	['edit-autoconfirmed-all-blp-temp']      = 'Wikipedia temporarily semi-protected biographies of living people',
	['edit-autoconfirmed-all-dispute-all']   = 'Wikipedia pages semi-protected due to dispute',
	['edit-autoconfirmed-all-sock-all']      = 'Wikipedia pages semi-protected from banned users',
	['edit-autoconfirmed-all-vandalism-all'] = 'Wikipedia pages semi-protected against vandalism',
	['edit-autoconfirmed-category-all-all']  = 'Wikipedia semi-protected categories',
	['edit-autoconfirmed-file-all-all']      = 'Semi-protected images',
	['edit-autoconfirmed-portal-all-all']    = 'Semi-protected portals',
	['edit-autoconfirmed-project-all-all']   = 'Semi-protected project pages',
	['edit-autoconfirmed-talk-all-all']      = 'Semi-protected talk pages',
	['edit-autoconfirmed-template-all-all']  = 'Wikipedia semi-protected templates',
	['edit-autoconfirmed-template-all-all']  = 'Wikipedia semi-protected templates',
	['edit-autoconfirmed-user-all-all']      = 'Wikipedia semi-protected user and user talk pages',
	['edit-sysop-all-blp-all']               = 'Wikipedia indefinitely protected biographies of living people',
	['edit-sysop-all-blp-temp']              = 'Wikipedia temporarily protected biographies of living people',
	['edit-sysop-all-dispute-all']           = 'Wikipedia pages protected due to dispute',
	['edit-sysop-all-sock-all']              = 'Wikipedia pages protected from banned users',
	['edit-sysop-all-vandalism-all']         = 'Wikipedia pages protected against vandalism',
	['edit-sysop-category-all-all']          = 'Wikipedia protected categories',
	['edit-sysop-file-all-all']              = 'Protected images',
	['edit-sysop-project-all-all']           = 'Protected project pages',
	['edit-sysop-talk-all-all']              = 'Protected talk pages',
	['edit-sysop-template-all-all']          = 'Wikipedia protected templates',
	['edit-sysop-user-all-all']              = 'Wikipedia protected user and user talk pages',
	['move-sysop-all-all-all']               = 'Wikipedia move-protected pages',
	['move-sysop-all-all-indef']             = 'Wikipedia indefinitely move-protected pages',
	['move-sysop-all-dispute-all']           = 'Wikipedia pages move-protected due to dispute',
	['move-sysop-all-vandalism-all']         = 'Wikipedia pages move-protected due to vandalism',
	['move-sysop-portal-all-all']            = 'Wikipedia move-protected portals',
	['move-sysop-portal-all-all']            = 'Wikipedia move-protected portals',
	['move-sysop-project-all-all']           = 'Wikipedia move-protected project pages',
	['move-sysop-talk-all-all']              = 'Wikipedia move-protected talk pages',
	['move-sysop-template-all-all']          = 'Wikipedia move-protected templates',
	['move-sysop-user-all-all']              = 'Wikipedia move-protected user and user talk pages',
	['autoreview-autoconfirmed-all-all-all']         = 'Wikipedia pending changes protected pages (level 1)',
	['autoreview-reviewer-all-all-all']              = 'Wikipedia pending changes protected pages (level 2)',
}

cfg.categoryNamespaces = {
	[2] = 'user',
	[3] = 'user',
	[4] = 'project',
	[6] = 'file',
	[10] = 'template',
	[12] = 'project',
	[14] = 'category',
	[100] = 'portal',
}

cfg.pagetypeNamespaces = {
	[0] = 'article',
	[6] = 'file',
	[10] = 'template',
	[14] = 'category',
	[828] = 'module',
	default = 'page'
}

--[[
-- Not currently used
local error_categories = {
	incorrect = 'Wikipedia pages with incorrect protection templates',
	no_expiry = 'Wikipedia protected pages without expiry',
	create = 'Wikipedia pages tagged as create-protected',
	template = 'Wikipedia template-protected pages other than templates and modules'
}
--]]

--------------------------------------------------------------------------------
-- Main functions
--------------------------------------------------------------------------------

-- Initialise necessary modules.
local mArguments = require('Module:Arguments')
local mProtectionLevel = require('Module:Effective protection level')._main
local mMessageBox -- only needs to be loaded if we are outputting a banner, so lazily initialise

-- Define often-used functions as local variables.
local tconcat = table.concat
local tinsert = table.insert
local tremove = table.remove
local ceil = math.ceil
local format = string.format

local function toTableEnd(t, pos)
	-- Sends the value at position pos to the end of array t, and shifts the
	-- other items down accordingly.
	return tinsert(t, tremove(t, pos))
end

local p = {}

function p.main(frame)
	local args = mArguments.getArgs(frame)
	return p._main(args)
end

function p._main(args)
	local title
	if args.page then
		title = mw.title.new(args.page)
	else
		title = mw.title.getCurrentTitle()
	end
	local protectionData = p.getProtectionData(title)
end

function p.getProtectionData(title)
	-- Gets a table containing protection data for the given title. The data
	-- is cached using a metatable, and so can be indexed as needed without
	-- a performance loss.
	local protectionData = {}
	local actions = {
		create = true,
		edit = true,
		move = true,
		autoreview = true
	}
	setmetatable(protectionData, {
		__index = function (t, key)
			local level
			if actions[key] then
				level = mProtectionLevel(key, title)
				if level == 'accountcreator' then
					-- Lump titleblacklisted pages in with template-protected pages,
					-- since templateeditors can do both.
					level = 'templateeditor'
				end
			end
			protectionData[key] = level
			return level
		end
	})
	return protectionData
end	

function p.getPagetype(ns)
	-- Returns a string with the page's type. Takes a namespace number as input.
	local pagetype = pagetypeNamespaces[ns] or pagetypeNamespaces.default
	if not pagetype then
		error('the page type could not be found; please define a name for the key "default"')
	end
	return pagetype
end

function p.matchNamespace(ns)
	-- Matches a namespace number to a string that can be passed to the
	-- namespace parameter of p.getCategoryName.
	if not ns or type(ns) ~= 'number' then
		return nil
	end
	local nskey = cfg.categoryNamespaces[ns]
	if not nskey and ns % 2 == 1 then
			nskey = 'talk'
	end
	return nskey
end

function p.getCategoryName(cats, action, level, namespace, reason, expiry)
	--[[
	-- Gets a category name from the category table, given a combination of
	-- the protection type, the protection level, the namespace number, the
	-- reason for protection, and the expiry date.
	--]]
	cats = cats or cfg.categories

	--[[
	-- Define the properties table. Each property is a table containing the
	-- canonical order that the property is tested in, the position the
	-- property has in the category key strings, and the property value itself.
	--]]
	local properties = {
		expiry = {order = 1, keypos = 5, val = expiry},
		namespace = {order = 2, keypos = 3, val = p.matchNamespace(namespace)},
		reason = {order = 3, keypos = 4, val = reason},
		level = {order = 4, keypos = 2, val = level},
		action = {order = 5, keypos = 1, val = action}
	}

	--[[
	-- Load the category order configuration for the reason specified.
	-- The configuration is stored in the categoryOrder field of each reason
	-- subtable of cfg.reasons. If the value is a table, then the order is the
	-- values specified in the table. If the value is a string, then the
	-- property corresponding to that string is tested last (i.e. it is the most
	-- important, because it keeps its specified value the longest) and the
	-- other properties are tested in the canonical order. If the value is of
	-- any other type then the canonical order is used.
	--]]
	local reasonTable = reason and cfg.reasons[reason]
	local categoryOrder = reasonTable and reasonTable.categoryOrder
	local categoryOrderType = type(categoryOrder)
	local configOrder = {}
	if categoryOrderType == 'table' then
		local dupes = {}
		for i = 1, 5 do
			local propertiesKey = categoryOrder[i]
			if not propertiesKey then
				local msg = 'no entry found for key '
					.. i
					.. ' in the cfg.reasons.'
					.. reason
					.. '.categoryOrder table'
				error(msg)
			end
			local property = properties[propertiesKey]
			if not property then
				local msg = 'invalid value "'
					.. propertiesKey
					.. '" detected in the cfg.reasons.'
					.. reason
					.. '.categoryOrder table'
				error(msg)
			end
			if dupes[propertiesKey] then
				local msg = 'duplicate values "'
					.. propertiesKey
					.. '" detected in the cfg.reasons.'
					.. reason
					.. '.categoryOrder table'
				error(msg)
			else
				dupes[propertiesKey] = true
			end
			configOrder[i] = property
		end
	else
		for propertiesKey, t in pairs(properties) do
			configOrder[t.order] = t
		end
		if categoryOrderType == 'string' then
			local property = properties[categoryOrder]
			if not property then
				local msg = '"'
					.. categoryOrder
					.. '" is not a valid value of cfg.reasons.'
					.. reason
					.. '.categoryOrder'
				error(msg)
			end
			toTableEnd(configOrder, property.order)
		end
	end

	--[[
	-- Define the attempt order. Properties with no value defined are moved
	-- to the end, where they will later be given the value "all". This is
	-- to cut down on the number of table lookups in the cats table, which
	-- grows exponentially with the number of properties with valid values.
	-- We keep track of the number of active properties with the noActive
	-- parameter.
	--]]
	local active, inactive = {}, {}
	for i, t in ipairs(configOrder) do
		if t.val then
			active[#active + 1] = t
		else
			inactive[#inactive + 1] = t
		end
	end
	local noActive = #active
	local attemptOrder = active
	for i, t in ipairs(inactive) do
		attemptOrder[#attemptOrder + 1] = t
	end

	--[[
	-- Check increasingly generic key combinations until we find a match.
	-- If a specific category exists for the combination of properties
	-- we are given, that match will be found first. If not, we keep
	-- trying different key combinations until we match using the key
	-- "all-all-all-all-all".
	--
	-- To generate the keys, we index the property subtables using a
	-- binary matrix with indexes i and j. j is only calculated up to
	-- the number of active properties. For example, if there were three
	-- active properties, the matrix would look like this, with 0
	-- corresponding to the string "all", and 1 corresponding to the
	-- val field in the property table:
	-- 
	--   j 1  2  3
	-- i  
	-- 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 properties are set
	-- to the string "all".
	--
	-- A key for the category table is constructed for each value of i.
	-- The correct position of the value in the key is determined by the
	-- pos field in the property table.
	--]]
	for i = 1, 2^noActive do
		local key = {}
		for j, t in ipairs(attemptOrder) do
			if j > noActive then
				key[t.keypos] = 'all'
			else
				local quotient = i / 2 ^ (j - 1)
				quotient = ceil(quotient)
				if quotient % 2 == 1 then
					key[t.keypos] = t.val
				else
					key[t.keypos] = 'all'
				end
			end
		end
		key = tconcat(key, '-')
		local attempt = cats[key]
		if attempt then
			return attempt
		end
	end
	error(
		'No category match found;'
		.. ' please define the category for key "all-all-all-all-all"'
	)
end

function p.renderImageLink(image, size, link, text, alt)
	--[[
	-- Renders the image link wikitext All parameters are optional
	-- apart from the display text.
	--
	-- @parameters:
	-- image - the image name
	-- size - the image size, as a number
	-- link - page linked to by the image
	-- text - the tooltip text
	-- alt - the alt text
	--
	-- All parameters are optional apart from the text parameter.
	--]]
	image = image or 'Transparent.gif'
	size = size or 20
	if link then
		link = '|link=' .. link
	else
		link = ''
	end
	text = text or error('No text parameter supplied to p.renderImageLink')
	if alt then
		alt = '|alt=' .. alt
	else
		alt = ''
	end
	return string.format('[[Image:%s|%dpx%s|%s%s]]', image, size, link, text, alt)
end

function p.renderPadlock(image, right)
	--[[
	-- Renders the html of the padlock seen in the top-right-hand corner
	-- of protected pages.
	-- 
	-- @parameters:
	-- image - the image wikitext (required)
	-- right - the "right" css property value, as a string (optional)
	--]]
	image = image or error('No image parameter specified in p.renderPadlock')
	local root = mw.html.create('div')
	root
		:addClass('metadata topicon nopopups')
		:attr('id', 'protected-icon')
		:css{display = 'none', right = right or '55px'}
		:wikitext(image)
	return tostring(root)
end

function p.renderBanner(page, image, text)
	--[[
	-- Renders the large protection banner placed at the top of articles,
	-- using the data provided in the data table.
	-- 
	-- @parameters:
	-- page - demo page parameter to pass to {{mbox}}
	-- image - the image wikitext
	-- text - the text to display
	--
	-- All parameters are optional.
	--]]
	mMessageBox = require('Module:Message box')
	local mbargs = { -- arguments for the message box module
		page = page,
		type = 'protection',
		image = image,
		text = text
	}
	return mMessageBox.main('mbox', mbargs)
end

--[[
When to add "Category:Wikipedia pages with incorrect protection templates":
* If it uses a type which is incompatible with the actual protection level of a page, or
* If an expiry date is set and is in the past

When to add "Category:Wikipedia protected pages without expiry":
* No expiry is set, and
* Expiry can be set, and
* The protection type is not "move" (could change this?), and
* The reason doesn't have "indefinitely protected" categories (pp-blp, pp-semi-indef and pp-move-indef have these), and
* The reason is not the generic pp-protected (could change this?)
(This leaves reasons "dispute", "vandalism", "usertalk", and "sock")
]]

return p