편집을 취소할 수 있습니다. 이 편집을 되돌리려면 아래의 바뀐 내용을 확인한 후 게시해주세요.
최신판 | 당신의 편집 | ||
3번째 줄: | 3번째 줄: | ||
-- Initialise necessary modules. | -- Initialise necessary modules. | ||
local mArguments = require('Module:Arguments') | |||
local | local mProtectionLevel = require('Module:Effective protection level')._main | ||
local | |||
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 | |||
-- | |||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- | -- Configuration | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local | 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.images = { | |||
- | full = 'Padlock.svg', | ||
semi = 'Padlock-silver.svg', | |||
template = 'Padlock-pink.svg', | |||
pc1 = 'Padlock-silver-light.svg', | |||
pc2 = 'Padlock-orange.svg', | |||
move = 'Padlock-olive.svg', | |||
indef = 'Padlock-red.svg', | |||
office = 'Padlock-black.svg', | |||
create = 'Padlock-skyblue.svg' | |||
} | |||
-------------------------------------------------------------------------------- | 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' | |||
' | |||
} | } | ||
function | -------------------------------------------------------------------------------- | ||
-- 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 | local obj = {} | ||
if | setmetatable(obj, { | ||
obj. | __index = banner | ||
}) | |||
obj. | |||
-- 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 | end | ||
-- | -- Get the protection level of the title object for the given protection | ||
-- action. This is always a string, even for invalid actions. | |||
obj. | do | ||
local protectionData = p.getProtectionData(obj.title) | |||
local protectionLevel = protectionData[obj.action] | |||
obj.protectionLevel = protectionLevel or '*' | |||
end | end | ||
-- | -- Fetch the banner data and copy it into the object. | ||
if | -- This could be problematic if the data table and the banner object have | ||
-- any duplicate keys, so check for those. | |||
-- | |||
do | do | ||
local data | |||
local | if args.reason | ||
if cfg.banners[obj.action] | and cfg.banners[obj.action] | ||
and cfg.banners[obj.action][args.reason] | |||
then | |||
data = cfg.banners[obj.action][obj.reason] | |||
elseif cfg.defaultBanners[obj.action] then | |||
data = cfg.defaultBanners[obj.action] | |||
elseif cfg.defaultBanners.edit then | |||
data = cfg.defaultBanners.edit | |||
else | |||
error('no banner data found; please define data for cfg.defaultBanners') | |||
end | |||
local usedFields = {} | |||
for k in pairs(banner) do | |||
usedFields[k] = true | |||
end | end | ||
for k in pairs(obj) do | |||
usedFields[k] = true | |||
end | end | ||
for k, v in pairs(data) do | |||
for | if usedFields[k] then | ||
error('banner.new: duplicate config key "' .. tostring(k) .. '" detected') | |||
else | |||
obj[k] = v | |||
end | end | ||
end | end | ||
end | end | ||
return | |||
-- Find whether we are outputting a padlock or a full banner. | |||
if obj.small == nil then | |||
-- Let the config data take precedence over user-specified arguments | |||
obj.small = yesno(args.small) or false | |||
end | |||
return obj | |||
end | end | ||
function | 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 | end | ||
function | 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 | end | ||
function | 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 | ||
-- | function banner:export() | ||
-- Add the banner/padlock, protection category, and tracking categories. | |||
local ret = '' | |||
ret = ret .. self:renderBannerOrPadlock() | |||
ret = ret .. self:renderProtectionCategory() | |||
ret = ret .. self:renderTrackingCategories() | |||
return ret | |||
end | |||
function | -------------------------------------------------------------------------------- | ||
return | -- Main functions | ||
-------------------------------------------------------------------------------- | |||
local p = {} | |||
function p.main(frame) | |||
local args = mArguments.getArgs(frame) | |||
return p._main(args) | |||
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 | end | ||
function | function p.getPagetype(ns) | ||
if not | -- 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 | 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 | end | ||
local nskey = cfg.categoryNamespaces[ns] | |||
if not nskey and ns % 2 == 1 then | |||
local | nskey = 'talk' | ||
if not | |||
end | end | ||
return nskey | |||
end | |||
-- | function p.getCategoryName(cats, action, level, namespace, reason, expiry) | ||
-- | --[[ | ||
-- | -- Gets a category name from the category table, given a combination of | ||
-- the protection type, the protection level, the namespace number, the | |||
-- reason for protection, and the expiry date. | |||
--]] | |||
cats = cats or cfg.categories | |||
--[[ | --[[ | ||
-- | -- 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. | |||
-- | |||
-- the | |||
--]] | --]] | ||
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 | local reasonTable = reason and cfg.reasons[reason] | ||
local categoryOrder = reasonTable and reasonTable.categoryOrder | |||
local categoryOrderType = type(categoryOrder) | |||
for i, | local configOrder = {} | ||
if | 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 | 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 | end | ||
toTableEnd(configOrder, property.order) | |||
end | 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 | ||
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. | -- Check increasingly generic key combinations until we find a match. | ||
-- specific category exists for the combination of | -- If a specific category exists for the combination of properties | ||
-- given, that match will be found first. If not, we keep | -- we are given, that match will be found first. If not, we keep | ||
-- key | -- trying different key combinations until we match using the key | ||
-- "all-all-all-all-all". | -- "all-all-all-all-all". | ||
-- | -- | ||
-- To generate the keys, we index the | -- To generate the keys, we index the property subtables using a | ||
-- with indexes i and j. j is only calculated up to the number of active | -- 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 | -- j 1 2 3 | ||
290번째 줄: | 566번째 줄: | ||
-- 8 0 0 0 | -- 8 0 0 0 | ||
-- | -- | ||
-- Values of j higher than the number of active | -- Values of j higher than the number of active properties are set | ||
-- to the string "all". | -- to the string "all". | ||
-- | -- | ||
-- A key for | -- A key for the category table is constructed for each value of i. | ||
-- The position of the value in the key is determined by the | -- 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 | for i = 1, 2^noActive do | ||
local key = {} | local key = {} | ||
313번째 줄: | 588번째 줄: | ||
end | end | ||
end | end | ||
key = table.concat(key, ' | key = table.concat(key, '-') | ||
local attempt = cats[key] | local attempt = cats[key] | ||
if attempt then | if attempt then | ||
return | return attempt | ||
end | end | ||
end | end | ||
error( | |||
'No category match found;' | |||
.. ' please define the category for key "all-all-all-all-all"' | |||
) | ) | ||
end | end | ||
--[[ | |||
When to add "Category:Wikipedia pages with incorrect protection templates": | |||
* If it uses a type which is incompatible with the actual protection level of a page, or | |||
* If an expiry date is set and is in the past | |||
When to add "Category:Wikipedia protected pages without expiry": | |||
* No expiry is set, and | |||
* Expiry can be set, and | |||
* The protection type is not "move" (could change this?), and | |||
* The reason doesn't have "indefinitely protected" categories (pp-blp, pp-semi-indef and pp-move-indef have these), and | |||
* The reason is not the generic pp-protected (could change this?) | |||
(This leaves reasons "dispute", "vandalism", "usertalk", and "sock") | |||
]] | |||
return p | return p |