Module:Protection banner: Difference between revisions
(make outline of the main function) |
(start converting this to an object-oriented approach - the banner seems naturally suited to object-based code) |
||
Line 1: | Line 1: | ||
-- This module implements {{pp-meta}} and its daughter templates such as | -- This module implements {{pp-meta}} and its daughter templates such as | ||
-- {{pp-dispute}}, {{pp-vandalism}} and {{pp-sock}}. | -- {{pp-dispute}}, {{pp-vandalism}} and {{pp-sock}}. | ||
-- 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 | |||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
Line 7: | Line 12: | ||
local cfg = {} | local cfg = {} | ||
cfg.defaultBanners = { | |||
edit = {}, | |||
move = {}, | |||
create = {}, | |||
autoreview = {} | |||
} | |||
cfg.banners = { | cfg.banners = { | ||
Line 183: | Line 195: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- | -- Helper functions | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local function toTableEnd(t, pos) | local function toTableEnd(t, pos) | ||
-- Sends the value at position pos to the end of array t, and shifts the | -- Sends the value at position pos to the end of array t, and shifts the | ||
-- other items down accordingly. | -- other items down accordingly. | ||
return | return table.insert(t, table.remove(t, pos)) | ||
end | |||
-------------------------------------------------------------------------------- | |||
-- Banner object | |||
-------------------------------------------------------------------------------- | |||
local banner = {} | |||
function banner.new(args) | |||
local obj = {} | |||
setmetatable(obj, { | |||
__index = banner | |||
}) | |||
-- Set the protection action. | |||
-- This is the action we are supposed to be protecting the page against, | |||
-- and does not necessarily correspond to the actual protection status. | |||
obj.action = args.action or 'edit' | |||
-- Get the title object of the page we are working on. | |||
if args.page then | |||
obj.title = mw.title.new(args.page) | |||
else | |||
obj.title = mw.title.getCurrentTitle() | |||
end | |||
-- Get the protection level of the title object for the given protection | |||
-- action. This is always a string, even for invalid actions. | |||
do | |||
local protectionData = p.getProtectionData(obj.title) | |||
local protectionLevel = protectionData[obj.action] | |||
obj.protectionLevel = protectionLevel or '*' | |||
end | |||
-- Fetch the banner data. | |||
obj.reason = args.reason | |||
if obj.reason | |||
and cfg.banners[obj.action] | |||
and cfg.banners[obj.action][obj.reason] | |||
then | |||
obj.data = cfg.banners[obj.action][obj.reason] | |||
elseif cfg.defaultBanners and cfg.defaultBanners[obj.action] then | |||
obj.data = cfg.defaultBanners[obj.action] | |||
elseif cfg.defaultBanners and cfg.defaultBanners.edit then | |||
obj.data = cfg.defaultBanners.edit | |||
else | |||
error('no banner data found; please define data for cfg.defaultBanners.edit') | |||
end | |||
return obj | |||
end | |||
function banner: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 banner: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 banner: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 | ||
-------------------------------------------------------------------------------- | |||
-- Main functions | |||
-------------------------------------------------------------------------------- | |||
local p = {} | local p = {} | ||
Line 212: | Line 337: | ||
function p._main(args) | function p._main(args) | ||
-- | -- Find what action we are supposed to be protecting the page from. | ||
-- | -- Add the banner/padlock, protection category, and tracking categories. | ||
local ret = '' | local ret = '' | ||
ret = ret .. p.renderBannerOrPadlock(protectionLevel, args) | ret = ret .. p.renderBannerOrPadlock(action, protectionLevel, args) | ||
ret = ret .. p.renderProtectionCategory(action, protectionLevel, args) | |||
ret = ret .. p.renderTrackingCategories(action, protectionLevel, args) | |||
ret = ret .. p.renderTrackingCategories(protectionLevel, args) | |||
return ret | return ret | ||
end | end | ||
Line 268: | Line 375: | ||
}) | }) | ||
return protectionData | return protectionData | ||
end | end | ||
function p.getPagetype(ns) | function p.getPagetype(ns) | ||
Line 439: | Line 546: | ||
else | else | ||
local quotient = i / 2 ^ (j - 1) | local quotient = i / 2 ^ (j - 1) | ||
quotient = ceil(quotient) | quotient = math.ceil(quotient) | ||
if quotient % 2 == 1 then | if quotient % 2 == 1 then | ||
key[t.keypos] = t.val | key[t.keypos] = t.val | ||
Line 447: | Line 554: | ||
end | end | ||
end | end | ||
key = | key = table.concat(key, '-') | ||
local attempt = cats[key] | local attempt = cats[key] | ||
if attempt then | if attempt then | ||
Line 457: | Line 564: | ||
.. ' please define the category for key "all-all-all-all-all"' | .. ' please define the category for key "all-all-all-all-all"' | ||
) | ) | ||
end | end | ||
Revision as of 07:20, 28 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}}. -- 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 -------------------------------------------------------------------------------- -- Configuration -------------------------------------------------------------------------------- local cfg = {} cfg.defaultBanners = { edit = {}, move = {}, create = {}, autoreview = {} } cfg.banners = { -- A table of protection banners, sorted by protection type. -- $1 = Intro blurb, e.g. "This page is currently -- [[Help:Protection|protected]] from editing" -- $2 = "until" or "or until" depending on the expiry -- $3 = "disputes", with or without a section link -- $4 = the type of the page, e.g. "article", "template", or "page" -- $5 = A blurb "it has been protected for x years, x months and x days." -- $6 = the protection date -- $7 = {{vandal-m|username}} replacement edit = { blp = { text = '$1 to promote compliance with' .. '[[Wikipedia:Biographies of living persons' .. "|Wikipedia's policy on the biographies" .. ' of living people]]', tooltip = '$1 to promote compliance with the policy on biographies of' .. ' living people', categoryOrder = 'reason', }, dispute = { text = '$1 $2 editing $3 have been resolved', tooltip = 'due to editing disputes', dispute = true, categoryOrder = 'reason', }, office = { text = 'This $4 is currently under the scrutiny of the' .. ' [[Wikipedia:Office actions|Wikimedia Foundation Office]]' .. ' and is protected. $5', categoryOrder = 'reason', }, reset = { text = 'On $6 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 = '$1 to prevent [[Wikipedia:Sock puppetry|sock puppets]] of' .. ' [[Wikipedia:Blocking policy|blocked]] or' .. ' [[Wikipedia:List of banned users|banned users]]' .. ' from editing it', tooltip = '$1 to prevent sock puppets of blocked or banned users from' .. ' editing it', categoryOrder = 'reason', }, usertalk = { text = '$1 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 = '$1 due to [[Wikipedia:Vandalism|vandalism]]', tooltip = '$1 due to vandalism', categoryOrder = 'namespace', } }, move = { dispute = { }, vandalism = { }, move = { } }, create = { }, autoreview = { pc1 = { }, pc2 = { } } } 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' } cfg.errorCategories = { incorrect = 'Wikipedia pages with incorrect protection templates', noExpiry = 'Wikipedia protected pages without expiry', create = 'Wikipedia pages tagged as create-protected', template = 'Wikipedia template-protected pages other than templates and modules' } -------------------------------------------------------------------------------- -- Helper functions -------------------------------------------------------------------------------- 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 table.insert(t, table.remove(t, pos)) end -------------------------------------------------------------------------------- -- Banner object -------------------------------------------------------------------------------- local banner = {} function banner.new(args) local obj = {} setmetatable(obj, { __index = banner }) -- Set the protection action. -- This is the action we are supposed to be protecting the page against, -- and does not necessarily correspond to the actual protection status. obj.action = args.action or 'edit' -- Get the title object of the page we are working on. if args.page then obj.title = mw.title.new(args.page) else obj.title = mw.title.getCurrentTitle() end -- Get the protection level of the title object for the given protection -- action. This is always a string, even for invalid actions. do local protectionData = p.getProtectionData(obj.title) local protectionLevel = protectionData[obj.action] obj.protectionLevel = protectionLevel or '*' end -- Fetch the banner data. obj.reason = args.reason if obj.reason and cfg.banners[obj.action] and cfg.banners[obj.action][obj.reason] then obj.data = cfg.banners[obj.action][obj.reason] elseif cfg.defaultBanners and cfg.defaultBanners[obj.action] then obj.data = cfg.defaultBanners[obj.action] elseif cfg.defaultBanners and cfg.defaultBanners.edit then obj.data = cfg.defaultBanners.edit else error('no banner data found; please define data for cfg.defaultBanners.edit') end return obj end function banner: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 banner: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 banner: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 -------------------------------------------------------------------------------- -- Main functions -------------------------------------------------------------------------------- local p = {} function p.main(frame) local args = mArguments.getArgs(frame) return p._main(args) end function p._main(args) -- Find what action we are supposed to be protecting the page from. -- Add the banner/padlock, protection category, and tracking categories. local ret = '' ret = ret .. p.renderBannerOrPadlock(action, protectionLevel, args) ret = ret .. p.renderProtectionCategory(action, protectionLevel, args) ret = ret .. p.renderTrackingCategories(action, protectionLevel, args) return ret 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 = math.ceil(quotient) if quotient % 2 == 1 then key[t.keypos] = t.val else key[t.keypos] = 'all' end end end key = table.concat(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 --[[ 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