Module:Protection banner: Difference between revisions

From Frontierpedia, the Microsoft Agent encyclopedia
en>Mr. Stradivarius
(save progress in simplifying the attempt order algorithm)
en>Mr. Stradivarius
(make the category name function errors more descriptive)
(20 intermediate revisions by the same user not shown)
Line 2: Line 2:
-- {{pp-dispute}}, {{pp-vandalism}} and {{pp-sock}}.
-- {{pp-dispute}}, {{pp-vandalism}} and {{pp-sock}}.


local categories = {
--------------------------------------------------------------------------------
-- Configuration
--------------------------------------------------------------------------------
 
local cfg = {}
 
cfg.reasons = {
blp = {
categoryOrder = 'reason',
banner = 'blp'
},
dispute = {
categoryOrder = 'reason',
banner = 'dispute'
},
office = {
categoryOrder = 'reason',
banner = 'office'
},
sock = {
categoryOrder = 'reason',
banner = 'sock'
},
usertalk = {
banner = 'usertalk'
},
vandalism = {
categoryOrder = 'namespace',
banner = 'vandalism'
}
}
 
cfg.banners = {
-- $1 = "until" or "or until" depending on the expiry
-- $2 = "disputes", with or without a section link
blp = {
},
dispute = {
text = '$1 editing $2 have been resolved',
tooltip = 'due to editing disputes',
dispute = true
},
office = {
},
sock = {
},
usertalk = {
text = '',
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]].'
},
vandalism = {
text = 'due to [[Wikipedia:Vandalism|vandalism]]',
tooltip = 'due to vandalism'
},
}
 
cfg.categories = {
-- The key strings follow this format:
-- The key strings follow this format:
-- type, level, ns, reason, expiry
-- type, level, ns, reason, expiry
['edit-autoconfirmed-user-all-all'] = 'Wikipedia semi-protected user and user talk pages',
['all-all-all-all-all']                  = 'Wikipedia protected pages',
['edit-autoconfirmed-project-all-all'] = 'Semi-protected project pages',
['all-all-all-office-all']              = 'Wikipedia Office-protected pages',
['edit-autoconfirmed-file-all-all'] = 'Semi-protected images',
['edit-all-template-all-all']            = 'Wikipedia protected templates',
['edit-autoconfirmed-template-all-all'] = 'Wikipedia semi-protected templates',
['edit-autoconfirmed-all-all-all']       = 'Wikipedia semi-protected pages',
['edit-autoconfirmed-portal-all-all'] = 'Semi-protected portals',
['edit-autoconfirmed-all-all-indef']     = 'Wikipedia indefinitely semi-protected pages',
['edit-autoconfirmed-talk-all-all'] = 'Semi-protected talk 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-all-vandalism-all'] = 'Wikipedia pages semi-protected against vandalism',
['edit-sysop-user-all-all'] = 'Wikipedia protected user and user talk pages',
['edit-autoconfirmed-category-all-all'] = 'Wikipedia semi-protected categories',
['edit-sysop-file-all-all'] = 'Protected images',
['edit-autoconfirmed-file-all-all']     = 'Semi-protected images',
['edit-sysop-project-all-all'] = 'Protected project pages',
['edit-autoconfirmed-portal-all-all']   = 'Semi-protected portals',
['edit-sysop-template-all-all'] = 'Wikipedia protected templates',
['edit-autoconfirmed-project-all-all']   = 'Semi-protected project pages',
['edit-sysop-talk-all-all'] = 'Protected talk pages',
['edit-autoconfirmed-talk-all-all']     = 'Semi-protected talk pages',
['edit-sysop-all-vandalism-all'] = 'Wikipedia pages protected against vandalism',
['edit-autoconfirmed-template-all-all'] = 'Wikipedia semi-protected templates',
['edit-autoconfirmed-all-dispute-all'] = 'Wikipedia pages semi-protected due to dispute',
['edit-autoconfirmed-template-all-all'] = 'Wikipedia semi-protected templates',
['edit-sysop-all-dispute-all'] = 'Wikipedia pages protected due to dispute',
['edit-autoconfirmed-user-all-all']     = 'Wikipedia semi-protected user and user talk pages',
['move-sysop-all-dispute-all'] = 'Wikipedia pages move-protected due to dispute',
['edit-sysop-all-blp-all']               = 'Wikipedia indefinitely protected biographies of living people',
['move-sysop-user-all-all'] = 'Wikipedia move-protected user and user talk pages',
['edit-sysop-all-blp-temp']             = 'Wikipedia temporarily protected biographies of living people',
['move-sysop-project-all-all'] = 'Wikipedia move-protected project pages',
['edit-sysop-all-dispute-all']           = 'Wikipedia pages protected due to dispute',
['move-sysop-portal-all-all'] = 'Wikipedia move-protected portals',
['edit-sysop-all-sock-all']             = 'Wikipedia pages protected from banned users',
['move-sysop-all-vandalism-all'] = 'Wikipedia pages move-protected due to vandalism',
['edit-sysop-all-vandalism-all']         = 'Wikipedia pages protected against vandalism',
['edit-autoconfirmed-template-all-all'] = 'Wikipedia semi-protected templates',
['edit-sysop-category-all-all']         = 'Wikipedia protected categories',
['move-sysop-template-all-all'] = 'Wikipedia move-protected templates',
['edit-sysop-file-all-all']             = 'Protected images',
['edit-all-template-all-all'] = 'Wikipedia protected templates',
['edit-sysop-project-all-all']           = 'Protected project pages',
['move-sysop-portal-all-all'] = 'Wikipedia move-protected portals',
['edit-sysop-talk-all-all']             = 'Protected talk pages',
['edit-autoconfirmed-all-sock-all'] = 'Wikipedia pages semi-protected from banned users',
['edit-sysop-template-all-all']         = 'Wikipedia protected templates',
['edit-sysop-all-sock-all'] = 'Wikipedia pages protected from banned users',
['edit-sysop-user-all-all']             = 'Wikipedia protected user and user talk pages',
['edit-autoconfirmed-all-blp-temp'] = 'Wikipedia temporarily semi-protected biographies of living people',
['move-sysop-all-all-all']               = 'Wikipedia move-protected pages',
['edit-sysop-all-blp-temp'] = 'Wikipedia temporarily protected biographies of living people',
['move-sysop-all-all-indef']             = 'Wikipedia indefinitely move-protected pages',
['edit-autoconfirmed-all-blp-all'] = 'Wikipedia indefinitely semi-protected biographies of living people',
['move-sysop-all-dispute-all']           = 'Wikipedia pages move-protected due to dispute',
['edit-sysop-all-blp-all'] = 'Wikipedia indefinitely protected biographies of living people',
['move-sysop-all-vandalism-all']         = 'Wikipedia pages move-protected due to vandalism',
['edit-autoconfirmed-all-all-indef'] = 'Wikipedia indefinitely semi-protected pages',
['move-sysop-portal-all-all']           = 'Wikipedia move-protected portals',
['edit-autoconfirmed-category-all-all'] = 'Wikipedia semi-protected categories',
['move-sysop-portal-all-all']           = 'Wikipedia move-protected portals',
['edit-sysop-category-all-all'] = 'Wikipedia protected categories',
['move-sysop-project-all-all']           = 'Wikipedia move-protected project pages',
['move-sysop-talk-all-all'] = 'Wikipedia move-protected talk pages',
['move-sysop-talk-all-all']             = 'Wikipedia move-protected talk pages',
['move-sysop-all-all-indef'] = 'Wikipedia indefinitely move-protected pages',
['move-sysop-template-all-all']         = 'Wikipedia move-protected templates',
['edit-autoconfirmed-all-all-all'] = 'Wikipedia semi-protected pages',
['move-sysop-user-all-all']             = 'Wikipedia move-protected user and user talk pages',
['move-sysop-all-all-all'] = 'Wikipedia move-protected pages',
['pc-autoconfirmed-all-all-all']         = 'Wikipedia pending changes protected pages (level 1)',
['pc-autoconfirmed-all-all-all'] = 'Wikipedia pending changes protected pages (level 1)',
['pc-reviewer-all-all-all']             = 'Wikipedia pending changes protected pages (level 2)',
['pc-reviewer-all-all-all'] = 'Wikipedia pending changes protected pages (level 2)',
['all-all-all-office-all'] = 'Wikipedia Office-protected pages',
['all-all-all-all-all'] = 'Wikipedia protected pages',
}
}


local nskeys = {
cfg.categoryNamespaces = {
[2] = 'user',
[2] = 'user',
[3] = 'user',
[3] = 'user',
Line 59: Line 122:
}
}


local behaviors = {
cfg.pagetypeNamespaces = {
vandalism = 'namespaceFirst',
[0] = 'article',
dispute = 'reasonFirst',
[6] = 'file',
blp = 'reasonFirst',
[10] = 'template',
sock = 'reasonFirst',
[14] = 'category',
office = 'reasonFirst',
[828] = 'module',
default = 'page'
}
}


Line 80: Line 144:
-- Main functions
-- Main functions
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Initialise necessary modules.
local mArguments = require('Module:Arguments')
local mMessageBox -- only needs to be loaded if we are outputting a banner, so lazily initialise


-- Define often-used functions as local variables.
-- Define often-used functions as local variables.
local tconcat = table.concat
local tconcat = table.concat
local floor = math.floor
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 = {}
local p = {}
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)
function p.matchNamespace(ns)
Line 91: Line 177:
-- namespace parameter of p.getCategoryName.
-- namespace parameter of p.getCategoryName.
if not ns or type(ns) ~= 'number' then
if not ns or type(ns) ~= 'number' then
return 'all'
return nil
end
end
local nskey = nskeys[ns]
local nskey = cfg.categoryNamespaces[ns]
if not nskey then
if not nskey and ns % 2 == 1 then
if ns % 2 == 1 then
nskey = 'talk'
nskey = 'talk'
else
nskey = 'all'
end
end
end
return nskey
return nskey
end
end


function p.getCategoryName(cats, protType, protLevel, namespace, reason, expiry)
function p.getCategoryName(cats, action, level, namespace, reason, expiry)
--[[
-- Gets a category name from the category table, given a combination of
-- Gets a category name from the category table, given a combination of
-- the protection type, the protection level, the namespace number, the
-- the protection type, the protection level, the namespace number, the
-- reason for protection, and the expiry date.
-- reason for protection, and the expiry date.
--]]
-- Preprocess parameters
cats = cats or cfg.categories
cats = cats or categories
 
protType = protType or 'all'
--[[
protLevel = protLevel or 'all'
-- Define the properties table. Each property is a table containing the
namespace = p.matchNamespace(namespace)
-- canonical order that the property is tested in, the position the
reason = reason or 'all'
-- property has in the category key strings, and the property value itself.
if not expiry then
--]]
expiry = 'all'
local properties = {
elseif expiry ~= 'indef' then
expiry = {order = 1, keypos = 5, val = expiry},
expiry = 'temp'
namespace = {order = 2, keypos = 3, val = p.matchNamespace(namespace)},
end
reason = {order = 3, keypos = 4, val = reason},
level = {order = 4, keypos = 2, val = level},
action = {order = 5, keypos = 1, val = action}
}


local properties = {expiry, namespace, protLevel, protType, reason}
--[[
local behavior = behaviors[reason]
-- Load the category order configuration for the reason specified.
if behavior == 'namespaceFirst' then
-- The configuration is stored in the categoryOrder field of each reason
tinsert(properties, table.remove(properties, 2)) -- move namespace to the end
-- subtable of cfg.reasons. If the value is a table, then the order is the
elseif behavior ~= 'reasonFirst' and reason ~= 'all' then
-- values specified in the table. If the value is a string, then the
error(reason .. ' is not a valid reason')
-- 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
end


local activeProperties = {}
--[[
for i, property in ipairs(properties) do
-- Define the attempt order. Properties with no value defined are moved
if property ~= 'all' then
-- to the end, where they will later be given the value "all". This is
activeProperties[#activeProperties + 1] = property
-- 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
end
end
local noActiveProperties = #activeProperties
local noActive = #active
local attemptOrder = active
-- Try successively generic matches until we run out of key combinations
for i, t in ipairs(inactive) do
for i = 1, 2^noActiveProperties 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 = {}
local key = {}
for pos = 1, 5 do
for j, t in ipairs(attemptOrder) do
if pos > noActiveProperties then
if j > noActive then
key[pos] = 'all'
key[t.keypos] = 'all'
else
else
local quotient = i / 2 ^ (pos - 1)
local quotient = i / 2 ^ (j - 1)
quotient = floor(quotient)
quotient = ceil(quotient)
if quotient % 2 == 1 then
if quotient % 2 == 1 then
key[pos] = protectionProperties[j]
key[t.keypos] = t.val
else -- quotient % 2 == 0
else
key[pos] = 'all'
key[t.keypos] = 'all'
end
end
end
end
end
end
key = tconcat(key, '-')
key = tconcat(key, '-')
mw.log(key) -- for debugging
local attempt = cats[key]
local attempt = cats[key]
if attempt then
if attempt then
Line 160: Line 347:
end
end
end
end
error('No category match found; please define the category for key "all-all-all-all-all"')
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
end


return p
return p

Revision as of 05:06, 19 March 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 = {
	blp = {
		categoryOrder = 'reason',
		banner = 'blp'
	},
	dispute = {
		categoryOrder = 'reason',
		banner = 'dispute'
	},
	office = {
		categoryOrder = 'reason',
		banner = 'office'
	},
	sock = {
		categoryOrder = 'reason',
		banner = 'sock'
	},
	usertalk = {
		banner = 'usertalk'
	},
	vandalism = {
		categoryOrder = 'namespace',
		banner = 'vandalism'
	}
}

cfg.banners = {
	-- $1 = "until" or "or until" depending on the expiry
	-- $2 = "disputes", with or without a section link
	blp = {
	},
	dispute = {
		text = '$1 editing $2 have been resolved',
		tooltip = 'due to editing disputes',
		dispute = true
	},
	office = {
	},
	sock = {
	},
	usertalk = {
		text = '',
		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]].'
	},
	vandalism = {
		text = 'due to [[Wikipedia:Vandalism|vandalism]]',
		tooltip = 'due to vandalism'
	},
}

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',
	['pc-autoconfirmed-all-all-all']         = 'Wikipedia pending changes protected pages (level 1)',
	['pc-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 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.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

return p