Module:Protection banner: Difference between revisions
Jump to navigation
Jump to search
Content added Content deleted
(allow passing the config object into the exportToLua and exportToWiki functions) |
(move the category methods to the Protection class, and get rid of all the category objects) |
||
Line 19: | Line 19: | ||
function Protection:initialize(args, configObj, titleObj) |
function Protection:initialize(args, configObj, titleObj) |
||
self._configObj = configObj |
|||
self._titleObj = titleObj |
|||
-- Set action |
-- Set action |
||
do |
do |
||
Line 123: | Line 126: | ||
function Protection:isProtected() |
function Protection:isProtected() |
||
return self.level ~= '*' |
return self.level ~= '*' |
||
end |
|||
function Protection._makeCategoryLink(cat) |
|||
-- Static method for rendering category wikitext. |
|||
if cat then |
|||
return string.format( |
|||
'[[%s:%s]]', |
|||
mw.site.namespaces[14].name, |
|||
cat |
|||
) |
|||
else |
|||
return '' |
|||
end |
|||
end |
|||
function Protection:makeProtectionCategory() |
|||
local configObj = self._configObj |
|||
local titleObj = self._titleObj |
|||
-- Exit if the page is not protected. |
|||
if not self:isProtected() then |
|||
return '' |
|||
end |
|||
-- Get the expiry. |
|||
local expiry = self.expiry |
|||
if type(expiry) == 'number' then |
|||
expiry = 'temp' |
|||
elseif expiry ~= 'indef' then |
|||
expiry = nil |
|||
end |
|||
-- Get the namespace category key. |
|||
local nskey |
|||
do |
|||
local namespace = titleObj.namespace |
|||
local categoryNamespaces = configObj.cfg.categoryNamespaceKeys |
|||
nskey = categoryNamespaces[namespace] |
|||
if not nskey and namespace % 2 == 1 then |
|||
nskey = 'talk' |
|||
end |
|||
end |
|||
-- Get the other inputs. |
|||
local reason = self.reason |
|||
local action = self.action |
|||
local level = self.level |
|||
--[[ |
|||
-- 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, val = expiry}, |
|||
namespace = {order = 2, val = nskey}, |
|||
reason = {order = 3, val = reason}, |
|||
level = {order = 4, val = level}, |
|||
action = {order = 5, val = action} |
|||
} |
|||
--[[ |
|||
-- Apply the category order configuration, if any. The configuration value |
|||
-- will be a property string, e.g. 'reason', 'namespace', etc. 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 no configuration |
|||
-- value is specified then the canonical order is used. |
|||
--]] |
|||
local configOrder = {} |
|||
do |
|||
local reasonsWithNamespacePriority = configObj.cfg.reasonsWithNamespacePriority |
|||
local namespaceFirst = reason and reasonsWithNamespacePriority[reason] or false |
|||
for propertiesKey, t in pairs(properties) do |
|||
configOrder[t.order] = t |
|||
end |
|||
if namespaceFirst then |
|||
-- Swap namespace and reason around. |
|||
local namespaceTable = table.remove(configOrder, 2) |
|||
table.insert(configOrder, 3, namespaceTable) |
|||
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 noActive, attemptOrder |
|||
do |
|||
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 |
|||
noActive = #active |
|||
attemptOrder = active |
|||
for i, t in ipairs(inactive) do |
|||
attemptOrder[#attemptOrder + 1] = t |
|||
end |
|||
end |
|||
--[[ |
|||
-- Check increasingly generic key combinations until we find a match. |
|||
-- If a specific category exists for the combination of 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. |
|||
--]] |
|||
local cats = configObj.cfg.protectionCategories |
|||
local cat |
|||
for i = 1, 2^noActive do |
|||
local key = {} |
|||
for j, t in ipairs(attemptOrder) do |
|||
if j > noActive then |
|||
key[t.order] = 'all' |
|||
else |
|||
local quotient = i / 2 ^ (j - 1) |
|||
quotient = math.ceil(quotient) |
|||
if quotient % 2 == 1 then |
|||
key[t.order] = t.val |
|||
else |
|||
key[t.order] = 'all' |
|||
end |
|||
end |
|||
end |
|||
key = table.concat(key, '-') |
|||
local attempt = cats[key] |
|||
if attempt then |
|||
cat = attempt |
|||
break |
|||
end |
|||
end |
|||
return self._makeCategoryLink(cat) |
|||
end |
|||
function Protection:makeExpiryCategory() |
|||
local reasonsWithoutExpiryCheck = self._configObj.cfg.reasonsWithoutExpiryCheck |
|||
local expiryCheckActions = self._configObj.cfg.expiryCheckActions |
|||
local cat |
|||
if not self.expiry |
|||
and expiryCheckActions[self.action] |
|||
and self.reason -- the old {{pp-protected}} didn't check for expiry |
|||
and not reasonsWithoutExpiryCheck[self.reason] |
|||
then |
|||
cat = self._configObj.msg['tracking-category-expiry'] |
|||
end |
|||
return self._makeCategoryLink(cat) |
|||
end |
|||
function Protection:makeErrorCategory() |
|||
local configObj = self._configObj |
|||
local cat |
|||
if not self:isProtected() |
|||
or type(self.expiry) == 'number' and self.expiry < os.time() |
|||
then |
|||
cat = configObj.msg['tracking-category-incorrect'] |
|||
end |
|||
return self._makeCategoryLink(cat) |
|||
end |
|||
function Protection:makeTemplateCategory() |
|||
local configObj = self._configObj |
|||
local titleObj = self._titleObj |
|||
local cat |
|||
if self.level == 'templateeditor' |
|||
and ( |
|||
(self.action ~= 'edit' and self.action ~= 'move') |
|||
or (titleObj.namespace ~= 10 and titleObj.namespace ~= 828) |
|||
) |
|||
then |
|||
cat = configObj.msg['tracking-category-template'] |
|||
end |
|||
return self._makeCategoryLink(cat) |
|||
end |
end |
||
Line 660: | Line 871: | ||
:wikitext(self:renderImage()) |
:wikitext(self:renderImage()) |
||
return tostring(root) |
return tostring(root) |
||
end |
|||
-------------------------------------------------------------------------------- |
|||
-- Category class |
|||
-------------------------------------------------------------------------------- |
|||
local Category = class('Category') |
|||
function Category:initialize(configObj, protectionObj) |
|||
self._configObj = configObj |
|||
self._protectionObj = protectionObj |
|||
end |
|||
function Category:setName(name) |
|||
self._name = name |
|||
end |
|||
function Category:render() |
|||
if self._name then |
|||
return string.format( |
|||
'[[%s:%s]]', |
|||
mw.site.namespaces[14].name, |
|||
self._name |
|||
) |
|||
end |
|||
end |
|||
-------------------------------------------------------------------------------- |
|||
-- ProtectionCategory class |
|||
-------------------------------------------------------------------------------- |
|||
local ProtectionCategory = Category:subclass('ProtectionCategory') |
|||
function ProtectionCategory:initialize(configObj, protectionObj, titleObj) |
|||
Category.initialize(self, configObj, protectionObj) |
|||
self._titleObj = titleObj |
|||
end |
|||
function ProtectionCategory:render() |
|||
local configObj = self._configObj |
|||
local protectionObj = self._protectionObj |
|||
local titleObj = self._titleObj |
|||
-- Get the level and exit if the page is not protected. |
|||
if not protectionObj:isProtected() then |
|||
return '' |
|||
end |
|||
-- Get the expiry. |
|||
local expiry = protectionObj.expiry |
|||
if type(expiry) == 'number' then |
|||
expiry = 'temp' |
|||
elseif expiry ~= 'indef' then |
|||
expiry = nil |
|||
end |
|||
-- Get the namespace category key. |
|||
local nskey |
|||
do |
|||
local namespace = titleObj.namespace |
|||
local categoryNamespaces = configObj.cfg.categoryNamespaceKeys |
|||
nskey = categoryNamespaces[namespace] |
|||
if not nskey and namespace % 2 == 1 then |
|||
nskey = 'talk' |
|||
end |
|||
end |
|||
-- Get the other inputs. |
|||
local reason = protectionObj.reason |
|||
local action = protectionObj.action |
|||
local level = protectionObj.level |
|||
--[[ |
|||
-- 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, val = expiry}, |
|||
namespace = {order = 2, val = nskey}, |
|||
reason = {order = 3, val = reason}, |
|||
level = {order = 4, val = level}, |
|||
action = {order = 5, val = action} |
|||
} |
|||
--[[ |
|||
-- Apply the category order configuration, if any. The configuration value |
|||
-- will be a property string, e.g. 'reason', 'namespace', etc. 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 no configuration |
|||
-- value is specified then the canonical order is used. |
|||
--]] |
|||
local configOrder = {} |
|||
do |
|||
local reasonsWithNamespacePriority = configObj.cfg.reasonsWithNamespacePriority |
|||
local namespaceFirst = reason and reasonsWithNamespacePriority[reason] or false |
|||
for propertiesKey, t in pairs(properties) do |
|||
configOrder[t.order] = t |
|||
end |
|||
if namespaceFirst then |
|||
-- Swap namespace and reason around. |
|||
local namespaceTable = table.remove(configOrder, 2) |
|||
table.insert(configOrder, 3, namespaceTable) |
|||
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 noActive, attemptOrder |
|||
do |
|||
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 |
|||
noActive = #active |
|||
attemptOrder = active |
|||
for i, t in ipairs(inactive) do |
|||
attemptOrder[#attemptOrder + 1] = t |
|||
end |
|||
end |
|||
--[[ |
|||
-- Check increasingly generic key combinations until we find a match. |
|||
-- If a specific category exists for the combination of 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. |
|||
--]] |
|||
local cats = configObj.cfg.protectionCategories |
|||
local cat |
|||
for i = 1, 2^noActive do |
|||
local key = {} |
|||
for j, t in ipairs(attemptOrder) do |
|||
if j > noActive then |
|||
key[t.order] = 'all' |
|||
else |
|||
local quotient = i / 2 ^ (j - 1) |
|||
quotient = math.ceil(quotient) |
|||
if quotient % 2 == 1 then |
|||
key[t.order] = t.val |
|||
else |
|||
key[t.order] = 'all' |
|||
end |
|||
end |
|||
end |
|||
key = table.concat(key, '-') |
|||
local attempt = cats[key] |
|||
if attempt then |
|||
cat = attempt |
|||
break |
|||
end |
|||
end |
|||
if cat then |
|||
self:setName(cat) |
|||
end |
|||
return Category.render(self) |
|||
end |
|||
-------------------------------------------------------------------------------- |
|||
-- ExpiryCategory class |
|||
-------------------------------------------------------------------------------- |
|||
local ExpiryCategory = Category:subclass('ExpiryCategory') |
|||
function ExpiryCategory:render() |
|||
local reasonsWithoutExpiryCheck = self._configObj.cfg.reasonsWithoutExpiryCheck |
|||
local expiryCheckActions = self._configObj.cfg.expiryCheckActions |
|||
local expiry = self._protectionObj.expiry |
|||
local action = self._protectionObj.action |
|||
local reason = self._protectionObj.reason |
|||
if not expiry |
|||
and expiryCheckActions[action] |
|||
and reason -- the old {{pp-protected}} didn't check for expiry |
|||
and not reasonsWithoutExpiryCheck[reason] |
|||
then |
|||
self:setName(self._configObj.msg['tracking-category-expiry']) |
|||
end |
|||
return Category.render(self) |
|||
end |
|||
-------------------------------------------------------------------------------- |
|||
-- ErrorCategory class |
|||
-------------------------------------------------------------------------------- |
|||
local ErrorCategory = Category:subclass('ErrorCategory') |
|||
function ErrorCategory:render() |
|||
local configObj = self._configObj |
|||
local protectionObj = self._protectionObj |
|||
local expiry = protectionObj.expiry |
|||
local action = protectionObj.action |
|||
local level = protectionObj.level |
|||
if not protectionObj:isProtected() |
|||
or type(expiry) == 'number' and expiry < os.time() |
|||
then |
|||
self:setName(configObj.msg['tracking-category-incorrect']) |
|||
end |
|||
return Category.render(self) |
|||
end |
|||
-------------------------------------------------------------------------------- |
|||
-- TemplateCategory class |
|||
-------------------------------------------------------------------------------- |
|||
local TemplateCategory = Category:subclass('TemplateCategory') |
|||
function TemplateCategory:initialize(configObj, protectionObj, titleObj) |
|||
Category.initialize(self, configObj, protectionObj) |
|||
self._titleObj = titleObj |
|||
end |
|||
function TemplateCategory:render() |
|||
local configObj = self._configObj |
|||
local titleObj = self._titleObj |
|||
local action = self._protectionObj.action |
|||
local level = self._protectionObj.level |
|||
local namespace = titleObj.namespace |
|||
if level == 'templateeditor' |
|||
and ( |
|||
(action ~= 'edit' and action ~= 'move') |
|||
or (namespace ~= 10 and namespace ~= 828) |
|||
) |
|||
then |
|||
self:setName(configObj.msg['tracking-category-template']) |
|||
end |
|||
return Category.render(self) |
|||
end |
end |
||
Line 986: | Line 932: | ||
-- Render the categories |
-- Render the categories |
||
if yesno(args.category) ~= false then |
if yesno(args.category) ~= false then |
||
ret[#ret + 1] = protectionObj:makeProtectionCategory() |
|||
local objects = { |
|||
ret[#ret + 1] = protectionObj:makeExpiryCategory() |
|||
ProtectionCategory:new(configObj, protectionObj, titleObj), |
|||
ret[#ret + 1] = protectionObj:makeErrorCategory() |
|||
ret[#ret + 1] = protectionObj:makeTemplateCategory() |
|||
TemplateCategory:new(configObj, protectionObj, titleObj) |
|||
} |
|||
for _, obj in ipairs(objects) do |
|||
ret[#ret + 1] = obj:render() |
|||
end |
|||
end |
end |
||
Line 1,008: | Line 949: | ||
Banner = Banner, |
Banner = Banner, |
||
Padlock = Padlock, |
Padlock = Padlock, |
||
Category = Category, |
|||
ProtectionCategory = ProtectionCategory, |
|||
ErrorCategory = ErrorCategory, |
|||
ExpiryCategory = ExpiryCategory, |
|||
} |
} |
||
end |
end |