require('strict')
local p = {}
local TableTools = require('Module:TableTools')
local yesno = require('Module:Yesno')
local checkForUnknownParameters = require('Module:Check for unknown parameters')._check
local categoryDatabase = mw.loadJsonData("Template:Contentious topics/Category database.json")
local restrictionsDatabase = mw.loadJsonData("Template:Contentious topics/Additional restrictions.json")
local restrictionsDefinition = mw.loadJsonData("Template:Contentious topics/Restrictions definition.json")
local standardSet = mw.loadJsonData("Template:Contentious topics/Standard set.json")
local subtopics = mw.loadJsonData("Template:Contentious topics/Subtopics.json")
local topicAliases = mw.loadJsonData("Template:Contentious topics/Aliases.json")
local function collectTopics(args, sectionParameter)
local seen = {}
local completeTopics = {}
local partialTopics = {}
local i = 2 -- initialize index
local keepGoing = true -- whether to keep checking for more CTOPs
local function add(value)
if value then
value = mw.ustring.lower(mw.text.trim(value))
local applicableSection = args[value .. '-section'] or sectionParameter
value = topicAliases[value] or value -- normalize to the canonical name after we find the applicableSection
if value ~= '' and not seen[value] then
if applicableSection then
partialTopics[value] = applicableSection
else
-- first we clear the partialTopics value
-- this code is needed for articles which are e.g. entirely Eastern Europe but partially [[WP:APL]]
partialTopics[value] = nil
-- then we add it to the list of completeTopics
table.insert(completeTopics, value)
end
seen[value] = true
keepGoing = true
end
end
end
-- Primary topic params
-- we manually add [1], t1, and topic1 to support {{ct/tn|topic=foo|topic2=bar}}
add(args[1])
add(args.t)
add(args.t1)
add(args.topic)
add(args.topic1)
-- Additional topics via numbered forms
while keepGoing do
keepGoing = false -- this is set back to true if any of the below are found
add(args[i])
add(args['t' .. i])
add(args['topic' .. i])
i = i + 1 -- increment the index
end
return completeTopics, partialTopics
end
function p.main(frame)
local args = require('Module:Arguments').getArgs(frame)
local sectionParameter = yesno(args.section, args.section) or yesno(args.relatedcontent, args.relatedcontent) -- whether we are sections all the way down
local completeTopics, partialTopics = collectTopics(args, sectionParameter) -- completeTopics is a sequence. partialTopics is a table with topics as keys and scope as values
local restrictions = {} -- A list of which restrictions are enabled for easier iteration
local restrictionFlags = {} -- Track which restrictions are enabled, as a set
local currentTitleObject = mw.title.getCurrentTitle()
local subjectTitleObject = currentTitleObject.subjectPageTitle
local underRestrictions -- a boolean for whether there are any active restrictions
local articleOrPage -- the string "article" (if a mainspace article) or "page" (if not)
local protectionLevel -- the edit protection level
local numberOfCompleteTopics = #completeTopics -- the number of complete topics
local numberOfPartialTopics = TableTools.size(partialTopics) -- the number of partial topics (not a sequence, so we have to use TableTools.size)
local numberOfTopics = numberOfCompleteTopics + numberOfPartialTopics -- total number of topics
local section = sectionParameter or numberOfPartialTopics > 0 -- whether any topics apply to parts of the article
local messageBody -- the text within the message box
local messageBox -- the message box itself
local unknownParameterCheck -- the result of [[Module:Check for unknown parameters]]
local unknownParameterTable -- the table to pass to the [[Module:Check for unknown parameters]] call
local categories = '' -- initialize categories
local manualRestrictions = false -- have we set any restrictions via |parameters?
--[[
This area sets active restrictions
The end goal is to get the restrictions variable into a nice, neat, sorted list of which restrictions are active
This is a somewhat intense process
--]]
-- Helpers to add a restriction if it's active and hasn't been added yet
local function maybeAddRestriction(restriction)
if yesno(args[restriction]) then
restrictionFlags[restriction] = true
manualRestrictions = true
end
end
local function alwaysAddRestriction(restriction)
restrictionFlags[restriction] = true
end
-- Helper to add a category
local function addCategory(cat)
if cat then
categories = categories .. '[[Category:' .. cat .. '|' .. currentTitleObject.text .. ']]'
end
end
-- Add the always-available restrictions
for _, r in ipairs(standardSet) do
maybeAddRestriction(r)
end
-- Topic-based restrictions
for _, topic in ipairs (completeTopics) do
local topicWide = restrictionsDatabase["topic-wide"][topic]
if topicWide then
for _, restriction in ipairs(topicWide) do
alwaysAddRestriction(restriction)
end
end
local additional = restrictionsDatabase["additional-available"][topic]
if additional then
for _, restriction in ipairs(additional) do
maybeAddRestriction(restriction)
end
end
end
for topic, scope in pairs(partialTopics) do
local additional = restrictionsDatabase["additional-available"][topic]
if additional then
for _, restriction in ipairs(additional) do
maybeAddRestriction(restriction)
end
end
local always = restrictionsDatabase["topic-wide"][topic]
if always then
for _, restriction in ipairs(always) do
-- Allow disabling these restrictions (via |1RR=no or similar)
if yesno(args[restriction], true) or args[restriction] == nil then
restrictionFlags[restriction] = true
else
restrictionFlags[restriction] = nil
end
end
end
end
-- Add the protection level
if yesno(args.protection, true) or yesno(args.aeprotection, true) then
protectionLevel = subjectTitleObject.protectionLevels["edit"][1]
if protectionLevel then
-- we have a |protection=foo parameter, and the page is protected
if restrictionFlags["ECR"] then
-- handle ECR with protection correctly
if protectionLevel == "full" then alwaysAddRestriction("full") end
else
-- no ECR, so just add the protection as normal
alwaysAddRestriction(protectionLevel)
end
manualRestrictions = true
else
-- we have a |protection=foo parameter, but the page is *not* protected
addCategory(categoryDatabase['protection-error'])
end
end
--[[
Clear duplicate restrictions (e.g. 0RR and 1RR; consensus-required is stronger than BRD)
--]]
-- if 0RR, then clear 1RR
if restrictionFlags["0RR"] then
restrictionFlags["1RR"] = nil
end
-- clear BRD if consensus-required is enabled
if restrictionFlags["consensus-required"] then
restrictionFlags["BRD"] = nil
end
-- and finally, convert our set to a list to make it easy to work with
restrictions = TableTools.keysToList(restrictionFlags, false, true)
--[[
Restrictions are now all set. Here, we add additional helper functions and variables necessary for generating the banner
--]]
-- Check whether any of the added restrictions are enabled
underRestrictions = #restrictions > 0 or args.other or args.other1
-- Determines whether we should use the string "article" or "page"
local articleOrPage = currentTitleObject:inNamespaces(1) and "article" or "page"
local function addToMessage(s)
messageBody = messageBody .. s
end
-- helper to call the appropriate parameter from [[Template:Contentious topics/list]]
local function callFromList(code, arg)
return frame:expandTemplate{
title = "Contentious topics/list/sandbox", -- REMOVE /sandbox when publishing
args = { [arg] = code }
}
end
local function getTopicBlurb(code)
return callFromList(code, 'scope')
end
local function getSuptopicBlurb(code)
return callFromList(code, 'subtopic')
end
-- returns the code for a superTopic
local function getSupertopic(code)
return callFromList(code, 'supertopic')
end
-- Makes a bullet point for a given contentious topic
-- the scope is either a string representing the exact scope of the topic
-- and is nil if it applies to the entire page or unspecified parts of the page
local function makeTopicBulletPoint(code, scope)
local topicBlurb = getTopicBlurb(code)
if topicBlurb == '' then
addCategory(categoryDatabase['bad-topic'])
else
if scope then
-- scope is not nil, so we write that into the bullet point
addToMessage('* <b>' .. topicBlurb .. '</b>, specifically the parts about <b>' .. scope .. '</b>')
else
-- scope is nil, so we have nothing to add
addToMessage('* <b>' .. topicBlurb .. '</b>')
end
-- check for a superTopic (e.g. South Asia is the superTopic of Indian military history)
local superTopic = subtopics[code]
if subtopics[code] then
-- we have a superTopic, so explain that
addToMessage(', a subtopic of <b>' .. getTopicBlurb(superTopic) .. '</b>\n')
else
addToMessage('\n')
end
end
end
-- Makes a restriction bullet point
local function makeRestrictionBulletPoint(code)
local def = restrictionsDefinition[code]
return def and ('* <b>' .. def .. '</b>\n') or ''
end
--[[
Error categories
--]]
-- No contentious topic codes
if numberOfTopics == 0 then
addCategory(categoryDatabase['no-topic'])
end
--[[
Begin building the messageBody
--]]
messageBody = '<b>The [[Wikipedia:Contentious topics|contentious topics]] procedure applies to this ' .. articleOrPage .. '.</b>'
-- if there's only one topic, we make a short blurb
if numberOfTopics == 1 then
local theCTOP
if section then
for topic, part in pairs(partialTopics) do
-- there's only one item, so this loop one runs once
addToMessage( ' Parts of this ' .. articleOrPage
.. (yesno(part, false) and '' or (' about <b>' .. part .. '</b>'))
.. ' relate')
theCTOP = topic
end
else
addToMessage(' This ' .. articleOrPage .. ' relates')
theCTOP = completeTopics[1]
end
local suptopicBlurb = getSuptopicBlurb(theCTOP)
if suptopicBlurb ~= '' then
addToMessage(' to <b>' .. suptopicBlurb .. '</b>, part of the contentious topic designation for <b>' .. getTopicBlurb(subtopics[theCTOP]) .. '</b>.')
else
addToMessage(' to <b>' .. getTopicBlurb(theCTOP) .. '</b>, a contentious topic.')
end
else
if numberOfCompleteTopics ~= 0 then
addToMessage('<p>The entire ' .. articleOrPage .. ' relates to ')
if numberOfCompleteTopics > 1 then
addToMessage('the following contentious topics:</p>\n')
for _, topic in ipairs(completeTopics) do
makeTopicBulletPoint(topic, nil)
end
else
addToMessage('<b>' .. getTopicBlurb(completeTopics[1]) .. '</b>')
local superTopic = subtopics[completeTopics[1]]
if superTopic then
addToMessage(', part of the contentious topic designation for <b>' .. getTopicBlurb(superTopic) .. '</b>.</p>')
else
addToMessage(', a contentious topic.</p>')
end
end
end
if numberOfPartialTopics ~= 0 then
addToMessage('<p>')
if numberOfCompleteTopics ~= 0 then
addToMessage('Additionally, parts ')
else
addToMessage('Parts ')
end
addToMessage('of this ' .. articleOrPage .. ' relate to ')
if numberOfPartialTopics > 1 then
addToMessage('the following contentious topics:</p>\n')
for topic, scope in pairs(partialTopics) do
if yesno(scope, false) then
-- the scope parameter is something like 'yes', which we can treat as nil
makeTopicBulletPoint(topic, nil)
else
makeTopicBulletPoint(topic, scope)
end
end
else
-- There's only one topic and scope, so this loop only runs once
for topic, scope in pairs(partialTopics) do
addToMessage('<b>' .. getTopicBlurb(topic) .. '</b>')
if yesno(scope, nil) == nil then
-- the scope is not a boolean value, so we have a free-text explanation of the applicable parts
addToMessage(', in particular the parts about <b>' .. scope .. '</b>')
end
local superTopic = subtopics[topic]
if superTopic then
addToMessage('. This is a subtopic of <b>' .. getTopicBlurb(superTopic) .. '</b>.</p>')
else
addToMessage('.</p>')
end
end
end
end
end
if underRestrictions then
messageBody = '<p style="margin-top:0"><strong style="text-transform: uppercase;">Warning: active arbitration remedies</strong></p>'
.. messageBody
.. '<p style="text-decoration:underline; text-align:center; font-size:120%;">The following restrictions apply to everyone editing this ' .. articleOrPage .. ':</p>\n'
for _, restriction in ipairs(restrictions) do
addToMessage(makeRestrictionBulletPoint(restriction))
addCategory(categoryDatabase[restriction])
end
if args.other or args.other1 then
-- we have some form of other restrictions, so add the category
addCategory(categoryDatabase["other"])
-- then define a helper function to add a restriction
local function addOther(s)
addToMessage('* <b>' .. s .. '</b>\n')
end
-- then add the generic 'other' parameter
if args.other then
addOther(args.other)
end
-- and now we loop to infinity and beyond
local i = 1
while true do
if args['other' .. i] then
addOther(args['other' .. i])
i = i + 1
else
break
end
end
end
end
addToMessage(' Editors are advised to familiarise themselves with the [[Wikipedia:Contentious topics|contentious topics procedures]] before editing this page.')
if not yesno(args.brief) then
addToMessage('<p>Editors who repeatedly or seriously fail to adhere to the [[WP:Five pillars|purpose of Wikipedia]], '
.. 'any expected [[WP:Etiquette|standards of behaviour]], '
.. 'or any [[WP:List of policies|normal editorial process]] may be blocked or restricted by an administrator.</p>')
end
if section then
addToMessage('<p>If it is unclear which parts of the page are related to this contentious topic, '
.. 'the content in question should be marked within the wiki text by an invisible comment. '
.. 'If no comment is present, please ask an administrator for assistance. If in doubt it is better to assume that the content is covered.</p>')
end
if underRestrictions then
if args['placed-date'] then
addToMessage('<p>Restrictions placed: ' .. require('Module:Format time')._main{args['placed-date']} .. '</p>')
elseif manualRestrictions then
addCategory(categoryDatabase['no-date'])
end
end
-- Now build the messageBox
messageBox = require('Module:Message box').main("tmbox", {
["type"] = underRestrictions and "delete" or "content",
["small"] = yesno(args.small),
["image"] = "[[File:Commons-emblem-"
.. (underRestrictions and "hand" or "issue")
.. ".svg|"
.. (yesno(args.small) and "30" or "40")
.. "px]]",
["text"] = messageBody
})
-- If ECR is enabled, prepend the ECR warning
if restrictionFlags["ECR"] then
messageBox = frame:expandTemplate{
title = "Template:Contentious topics/talk notice/ECR warning",
args = { section = section and "yes" or "", small = args.small}}
.. messageBox
-- Hard code for [[WP:BER]]
if TableTools.inArray(completeTopics, "a-i") then
messageBox = messageBox .. "<p class='PIA-flag' style='display:none; visibility:hidden;'>This page is subject to the extended confirmed restriction related to the Arab-Israeli conflict.</p>"
addCategory("Wikipedia pages subject to the extended confirmed restriction related to the Arab-Israeli conflict")
end
end
--[[
Categories!!!
We set the restriction categories back in the if underRestrictions loop
to avoid looping through the restrictions twice. So we only need to do some cleanup, check for unknown parameters, and handle nocat
Because nocat is only for the ultra-rare case of demonstration,
manually clearing the categories is more efficient
--]]
-- Start checking for unknown parameters
-- Doing so with extensible modules is annoying; [[Module:Check for unknown parameters]] is primarily intended for hardcoded stuff
-- luckily, it makes use of a table for its main settings, and we can do some clever stuff with that
-- helper to mark a parameter as known
local function addKnownParameter(s)
table.insert(unknownParameterTable, s)
end
-- initialize the table with the keys of restrictionsDefinition JSON
unknownParameterTable = TableTools.keysToList(restrictionsDefinition, false, true)
-- then some hardcoded parameters
addKnownParameter('aeprotection')
addKnownParameter('brief')
addKnownParameter('nocat')
addKnownParameter('placed-date')
addKnownParameter('protection')
addKnownParameter('relatedcontent')
addKnownParameter('section')
addKnownParameter('small')
-- then add all of the partialTopics section parameters
for code, _ in pairs(partialTopics) do
addKnownParameter(code .. '-section')
end
-- then all the various topic parameters. Table is no longer a sequence; perform any computation requring a sequence above
unknownParameterTable['regexp1'] = 'topic[%d]*'
unknownParameterTable['regexp2'] = 't[%d]*'
unknownParameterTable['regexp3'] = '[%d]+'
unknownParameterTable['regexp4'] = 'other[%d]*'
-- set the tracking category
unknownParameterTable['unknown'] = '[[Category:' .. categoryDatabase['unknown'] .. '|_VALUE_' .. currentTitleObject.text .. ']]'
if yesno(args.nocat) then
-- then handle nocat by clearing both category-holding parameters
categories = ''
unknownParameterCheck = ''
else
-- nocat is not specified, so we add the universal category
addCategory(categoryDatabase["all"])
-- and call the unknownParameterCheck
unknownParameterCheck = checkForUnknownParameters(unknownParameterTable, args)
end
return messageBox .. categories .. unknownParameterCheck
end
return p