More actions
Content deleted Content added
give up on the OOP approach, lay out the main module structure in more detail, and move the config to Module:Protection banner/config |
Yet Another Rewrite - this time using Module:Middleclass |
||
Line 3: | Line 3: | ||
-- Initialise necessary modules. |
-- Initialise necessary modules. |
||
local class = require('Module:Middleclass').class |
|||
local mArguments = require('Module:Arguments') |
local mArguments = require('Module:Arguments') |
||
local mProtectionLevel = require('Module:Effective protection level')._main |
|||
local mFileLink = require('Module:File link') |
local mFileLink = require('Module:File link') |
||
local mProtectionLevel = require('Module:Effective protection level') |
|||
local yesno = require('Module:Yesno') |
local yesno = require('Module:Yesno') |
||
local mMessageBox -- only needs to be loaded if we are outputting a banner, so lazily initialise |
|||
-- Load config |
|||
local cfg = mw.loadData('Module:Protection banner/config') |
|||
local p = {} |
|||
-------------------------------------------------------------------------------- |
-------------------------------------------------------------------------------- |
||
-- ProtectionStatus class |
|||
-- Helper functions |
|||
-------------------------------------------------------------------------------- |
-------------------------------------------------------------------------------- |
||
local ProtectionStatus = class('ProtectionStatus') |
|||
local function validateField(tName, t, field, expectType, nilOk) |
|||
local val = t[field] |
|||
function ProtectionStatus:initialize(args, titleObj) |
|||
local valType = type(val) |
|||
-- Set action |
|||
if not (valType == expectType or nilOk and valType == 'nil') then |
|||
do |
|||
error(string.format( |
|||
local actions = { |
|||
"type error in '%s' field '%s' (%s%s expected, got %s)", |
|||
create = true, |
|||
edit = true, |
|||
move = true, |
|||
autoreview = true |
|||
nilOk and ' or nil' or '', |
|||
} |
|||
valType |
|||
if args.action and actions[args.action] then |
|||
), 2) |
|||
self._action = args.action |
|||
else |
|||
self._action = 'edit' |
|||
end |
|||
end |
end |
||
-- Set level |
|||
do |
|||
local level = mProtectionLevel._main(self._action, titleObj) |
|||
if level == 'accountcreator' then |
|||
-- Lump titleblacklisted pages in with template-protected pages, |
|||
-- since templateeditors can do both. |
|||
level = 'templateeditor' |
|||
end |
|||
self._level = level or '*' |
|||
end |
|||
-- Set reason |
|||
self._reason = args.reason |
|||
-- Set expiry |
|||
self._expiry = args.expiry |
|||
end |
end |
||
function ProtectionStatus:getAction() |
|||
return self._action |
|||
-- 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 |
end |
||
function ProtectionStatus:getLevel() |
|||
-------------------------------------------------------------------------------- |
|||
return self._level |
|||
-- Main functions |
|||
end |
|||
-------------------------------------------------------------------------------- |
|||
function |
function ProtectionStatus:getReason() |
||
return self._reason |
|||
local args = mArguments.getArgs(frame) |
|||
return p._main(args) |
|||
end |
end |
||
function |
function ProtectionStatus:getExpiry() |
||
return self._expiry |
|||
local protectionLevel = p.getProtectionLevel(args) |
|||
local isSmall = yesno(args.small) or false |
|||
local bannerConfig = p.getBannerConfig(protectionLevel, args) |
|||
local ret = '' |
|||
if isSmall then |
|||
ret = ret .. p.makePadlock(protectionLevel, args, bannerConfig) |
|||
else |
|||
ret = ret .. p.makeBanner(protectionLevel, args, bannerConfig) |
|||
end |
|||
ret = ret .. p.getProtectionCategory(protectionLevel, args) |
|||
ret = ret .. p.getTrackingCategories(protectionLevel, args) |
|||
return ret |
|||
end |
end |
||
-------------------------------------------------------------------------------- |
-------------------------------------------------------------------------------- |
||
-- Config class |
|||
-- Protection functions |
|||
-------------------------------------------------------------------------------- |
-------------------------------------------------------------------------------- |
||
local Config = class('Config') |
|||
function p.getProtectionLevel(args) |
|||
local title |
|||
function Config:initialize() |
|||
if args.page then |
|||
self._cfg = mw.loadData('Module:Protection banner/config') |
|||
title = mw.title.new(args.page) |
|||
end |
|||
function Config:getBannerConfig(protectionStatusObj) |
|||
local cfg = self._cfg |
|||
local action = protectionStatusObj:getAction() |
|||
local reason = protectionStatusObj:getReason() |
|||
if cfg.banners[action][reason] then |
|||
return cfg.banners[action][reason] |
|||
else |
else |
||
return cfg.defaultBanners[action] |
|||
title = mw.title.getCurrentTitle() |
|||
end |
end |
||
local protectionData = p.getProtectionData(title) |
|||
protectionLevel = protectionData[args.action or 'edit'] |
|||
return protectionLevel or '*' |
|||
end |
end |
||
function |
function Config:getConfigTable(key) |
||
local whitelisted = { |
|||
-- Gets a table containing protection data for the given title. The data |
|||
images = true, |
|||
-- is cached using a metatable, and so can be indexed as needed without |
|||
categories = true, |
|||
-- a performance loss. |
|||
categoryNamespaces = true, |
|||
local protectionData = {} |
|||
pagetypeNamespaces = true, |
|||
local actions = { |
|||
errorCategories = true |
|||
edit = true, |
|||
move = true, |
|||
autoreview = true |
|||
} |
} |
||
if whitelisted[key] then |
|||
setmetatable(protectionData, { |
|||
return self._cfg[key] |
|||
__index = function (t, key) |
|||
else |
|||
local level |
|||
return nil |
|||
if actions[key] then |
|||
end |
|||
level = mProtectionLevel(key, title) |
|||
end |
|||
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 |
|||
-------------------------------------------------------------------------------- |
-------------------------------------------------------------------------------- |
||
-- Image class |
|||
-- Banner config functions |
|||
-------------------------------------------------------------------------------- |
-------------------------------------------------------------------------------- |
||
local Image = class('Image') |
|||
function p.getBannerConfig(protectionLevel, args) |
|||
end |
|||
-------------------------------------------------------------------------------- |
-------------------------------------------------------------------------------- |
||
-- Blurb class |
|||
-- Padlock functions |
|||
-------------------------------------------------------------------------------- |
-------------------------------------------------------------------------------- |
||
local Blurb = class('Blurb') |
|||
function p.makePadlock(protectionLevel, args, bannerConfig) |
|||
local data = p.makePadlockData(protectionLevel, args, bannerConfig) |
|||
function Blurb:initialize(bannerConfig) |
|||
return p.renderPadlock(data) |
|||
self._config = bannerConfig |
|||
end |
end |
||
-------------------------------------------------------------------------------- |
|||
function p.makePadlockData(protectionLevel, args, bannerConfig) |
|||
-- BannerTemplate class |
|||
-------------------------------------------------------------------------------- |
|||
local BannerTemplate = class('BannerTemplate') |
|||
function BannerTemplate:initialize() |
|||
end |
end |
||
function |
function BannerTemplate:render() |
||
data = data or {} |
|||
local image = mFileLink.new(data.filename or 'Transparent.gif') |
|||
:width(20) |
|||
:link(data.link) |
|||
:alt(data.alt) |
|||
:render() |
|||
local root = mw.html.create('div') |
|||
root |
|||
:addClass('metadata topicon nopopups') |
|||
:attr('id', 'protected-icon') |
|||
:css{display = 'none', right = data.right or '55px'} |
|||
:wikitext(image) |
|||
return tostring(root) |
|||
end |
end |
||
-------------------------------------------------------------------------------- |
-------------------------------------------------------------------------------- |
||
-- Banner |
-- Banner class |
||
-------------------------------------------------------------------------------- |
-------------------------------------------------------------------------------- |
||
local Banner = BannerTemplate:subclass('Banner') |
|||
function p.makeBanner(protectionLevel, args, bannerConfig) |
|||
local data = p.makeBannerData(protectionLevel, args, bannerConfig) |
|||
return p.renderBanner(data) |
|||
end |
|||
-------------------------------------------------------------------------------- |
|||
function p.makeBannerData(protectionLevel, args, bannerConfig) |
|||
-- Padlock class |
|||
end |
|||
-------------------------------------------------------------------------------- |
|||
local Padlock = BannerTemplate:subclass('Padlock') |
|||
function p.renderBanner(data) |
|||
data = data or {} |
|||
local image = mFileLink.new(data.filename) |
|||
:width(40) |
|||
:caption(data.mouseover) |
|||
:render() |
|||
local mbargs = { |
|||
page = data.page, |
|||
type = 'protection', |
|||
image = image, |
|||
text = string.format( |
|||
"'''%s'''%s", |
|||
data.reasonText, |
|||
data.explanationText and '<br />' .. data.explanationText or '' |
|||
) |
|||
} |
|||
return mMessageBox.main('mbox', mbargs) |
|||
end |
|||
-------------------------------------------------------------------------------- |
-------------------------------------------------------------------------------- |
||
-- Category class |
|||
-- Protection category functions |
|||
-------------------------------------------------------------------------------- |
-------------------------------------------------------------------------------- |
||
local Category = class('Category') |
|||
function p.getProtectionCategory(protectionLevel, args) |
|||
end |
|||
function |
function Category:initialize() |
||
-- 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 |
end |
||
function |
function Category:export() |
||
if self._categoryName then |
|||
-- Matches a namespace number to a string that can be passed to the |
|||
return string.format( |
|||
-- namespace parameter of p.getCategoryName. |
|||
'[[%s:%s]]', |
|||
if not ns or type(ns) ~= 'number' then |
|||
mw.site.namespaces[14].name, |
|||
return nil |
|||
self._categoryName |
|||
) |
|||
else |
|||
return '' |
|||
end |
end |
||
local nskey = cfg.categoryNamespaces[ns] |
|||
if not nskey and ns % 2 == 1 then |
|||
nskey = 'talk' |
|||
end |
|||
return nskey |
|||
end |
end |
||
-------------------------------------------------------------------------------- |
|||
function p.getCategoryName(cats, action, level, namespace, reason, expiry) |
|||
-- ProtectionCategory class |
|||
--[[ |
|||
-------------------------------------------------------------------------------- |
|||
-- 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 |
|||
local ProtectionCategory = Category:subclass('ProtectionCategory') |
|||
--[[ |
|||
-- 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} |
|||
} |
|||
-------------------------------------------------------------------------------- |
|||
--[[ |
|||
-- ErrorCategory class |
|||
-- 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 |
|||
local ErrorCategory = Category:subclass('ErrorCategory') |
|||
--[[ |
|||
-- 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 |
|||
-------------------------------------------------------------------------------- |
|||
--[[ |
|||
-- ExpiryCategory class |
|||
-- 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 |
|||
local ExpiryCategory = Category:subclass('ExpiryCategory') |
|||
-- 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 |
|||
-------------------------------------------------------------------------------- |
-------------------------------------------------------------------------------- |
||
-- ProtectionBanner class |
|||
-- Tracking category functions |
|||
-------------------------------------------------------------------------------- |
-------------------------------------------------------------------------------- |
||
local ProtectionBanner = {} |
|||
function p.getTrackingCategories(protectionLevel, args) |
|||
function ProtectionBanner._newBannerTemplate(args) |
|||
-- Makes a new banner template object. This will be a new instance of |
|||
-- the most suitable subclass of BannerTemplate, e.g. Banner or Padlock. |
|||
if yesno(args.small) then |
|||
return Padlock:new(args) |
|||
else |
|||
return Banner:new(args) |
|||
end |
|||
end |
end |
||
function ProtectionBanner.exportToWiki(frame, titleObj) |
|||
--[[ |
|||
local args = mArguments.getArgs(frame) |
|||
When to add "Category:Wikipedia pages with incorrect protection templates": |
|||
return ProtectionBanner.exportToLua(args, titleObj) |
|||
* If it uses a type which is incompatible with the actual protection level of a page, or |
|||
end |
|||
* If an expiry date is set and is in the past |
|||
function ProtectionBanner.exportToLua(args, title) |
|||
When to add "Category:Wikipedia protected pages without expiry": |
|||
title = title or mw.title.getCurrentTitle() |
|||
* No expiry is set, and |
|||
end |
|||
* 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 |
return ProtectionBanner |
Revision as of 16:26, 8 June 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 class = require('Module:Middleclass').class
local mArguments = require('Module:Arguments')
local mFileLink = require('Module:File link')
local mProtectionLevel = require('Module:Effective protection level')
local yesno = require('Module:Yesno')
--------------------------------------------------------------------------------
-- ProtectionStatus class
--------------------------------------------------------------------------------
local ProtectionStatus = class('ProtectionStatus')
function ProtectionStatus:initialize(args, titleObj)
-- Set action
do
local actions = {
create = true,
edit = true,
move = true,
autoreview = true
}
if args.action and actions[args.action] then
self._action = args.action
else
self._action = 'edit'
end
end
-- Set level
do
local level = mProtectionLevel._main(self._action, titleObj)
if level == 'accountcreator' then
-- Lump titleblacklisted pages in with template-protected pages,
-- since templateeditors can do both.
level = 'templateeditor'
end
self._level = level or '*'
end
-- Set reason
self._reason = args.reason
-- Set expiry
self._expiry = args.expiry
end
function ProtectionStatus:getAction()
return self._action
end
function ProtectionStatus:getLevel()
return self._level
end
function ProtectionStatus:getReason()
return self._reason
end
function ProtectionStatus:getExpiry()
return self._expiry
end
--------------------------------------------------------------------------------
-- Config class
--------------------------------------------------------------------------------
local Config = class('Config')
function Config:initialize()
self._cfg = mw.loadData('Module:Protection banner/config')
end
function Config:getBannerConfig(protectionStatusObj)
local cfg = self._cfg
local action = protectionStatusObj:getAction()
local reason = protectionStatusObj:getReason()
if cfg.banners[action][reason] then
return cfg.banners[action][reason]
else
return cfg.defaultBanners[action]
end
end
function Config:getConfigTable(key)
local whitelisted = {
images = true,
categories = true,
categoryNamespaces = true,
pagetypeNamespaces = true,
errorCategories = true
}
if whitelisted[key] then
return self._cfg[key]
else
return nil
end
end
--------------------------------------------------------------------------------
-- Image class
--------------------------------------------------------------------------------
local Image = class('Image')
--------------------------------------------------------------------------------
-- Blurb class
--------------------------------------------------------------------------------
local Blurb = class('Blurb')
function Blurb:initialize(bannerConfig)
self._config = bannerConfig
end
--------------------------------------------------------------------------------
-- BannerTemplate class
--------------------------------------------------------------------------------
local BannerTemplate = class('BannerTemplate')
function BannerTemplate:initialize()
end
function BannerTemplate:render()
end
--------------------------------------------------------------------------------
-- Banner class
--------------------------------------------------------------------------------
local Banner = BannerTemplate:subclass('Banner')
--------------------------------------------------------------------------------
-- Padlock class
--------------------------------------------------------------------------------
local Padlock = BannerTemplate:subclass('Padlock')
--------------------------------------------------------------------------------
-- Category class
--------------------------------------------------------------------------------
local Category = class('Category')
function Category:initialize()
end
function Category:export()
if self._categoryName then
return string.format(
'[[%s:%s]]',
mw.site.namespaces[14].name,
self._categoryName
)
else
return ''
end
end
--------------------------------------------------------------------------------
-- ProtectionCategory class
--------------------------------------------------------------------------------
local ProtectionCategory = Category:subclass('ProtectionCategory')
--------------------------------------------------------------------------------
-- ErrorCategory class
--------------------------------------------------------------------------------
local ErrorCategory = Category:subclass('ErrorCategory')
--------------------------------------------------------------------------------
-- ExpiryCategory class
--------------------------------------------------------------------------------
local ExpiryCategory = Category:subclass('ExpiryCategory')
--------------------------------------------------------------------------------
-- ProtectionBanner class
--------------------------------------------------------------------------------
local ProtectionBanner = {}
function ProtectionBanner._newBannerTemplate(args)
-- Makes a new banner template object. This will be a new instance of
-- the most suitable subclass of BannerTemplate, e.g. Banner or Padlock.
if yesno(args.small) then
return Padlock:new(args)
else
return Banner:new(args)
end
end
function ProtectionBanner.exportToWiki(frame, titleObj)
local args = mArguments.getArgs(frame)
return ProtectionBanner.exportToLua(args, titleObj)
end
function ProtectionBanner.exportToLua(args, title)
title = title or mw.title.getCurrentTitle()
end
return ProtectionBanner