Module:Sanctions

local getArgs = require('Module:Arguments').getArgs local yesno = require('Module:Yesno')

local rawData = mw.loadData('Module:Sanctions/data') local data = rawData.sanctions local aliasMap = rawData.aliases

local messageBox = require('Module:Message box')

-- Functions

local function tableContainsValue(needle, haystack) for _, v in pairs(haystack) do		if v == needle then return true end end return false end

local function _getTopicData(topicAlias) if data[topicAlias] then return data[topicAlias] elseif aliasMap[topicAlias] then return data[aliasMap[topicAlias]] else return false end end

-- Returns an invalid topic error, with a table of acceptable topics local function syntaxHelp return topic not specified. Available options:  end

-- Topic class

local Topic = {} Topic.__index = Topic

function Topic.new(topicCode, args) local obj = {} obj._args = args obj._topicData = _getTopicData(topicCode) return setmetatable(obj, Topic) end

function Topic:get(arg) return self._topicData[arg] end

function Topic:exists return ((self._topicData ~= nil) and (self._topicData ~= false)) end

function Topic:hasGlobalRestriction(type) return self._topicData.restrictions[type] end

function Topic:hasLocalRestriction(type) local localRestriction = self._args[type] if localRestriction then if mw.ustring.find(type, '^restriction') ~= nil then return true else return yesno(localRestriction) end else return false end end

function Topic:hasRestriction(type) return self:hasGlobalRestriction(type) or self:hasLocalRestriction(type) end

function Topic:hasRestrictions(arr) for _, v in ipairs(arr) do		if self:hasRestriction(v) then return true end end return false end

function Topic:hasAnyRevertRestrictions return self:hasRestrictions({'1rr', 'consensusrequired', 'brd'}) end

function Topic:hasAnyRestrictions return self:hasAnyRevertRestrictions or self:hasRestrictions({'restriction1'}) end

function Topic:getCustomRestrictions local customRestrictions = {} local ri = 1 local checkArr = self._topicData.restrictions local breakNext = false while true do		if checkArr['restriction'..ri] then table.insert(customRestrictions, checkArr['restriction'..ri]) ri = ri + 1 else if breakNext then break else ri = 1 checkArr = self._args breakNext = true end end end return customRestrictions end

-- End classes

-- This function builds a talk notice -- TODO: split this up -- -- @param frame -- @param topic topic class instance -- @param args arguments passed to wrapper template -- @returns String representation of notice local function buildTalkNotice(frame, topic, args) local type = args['type'] or args[2] or 'mini' local out = mw.html.create('') local hasRestrictions = topic:hasAnyRestrictions local hasRevertRestrictions = topic:hasAnyRevertRestrictions if hasRestrictions then type = 'long' -- force long displaywhere custom restrictions are applicable

out :tag('span') :css('font-size', '120%') :wikitext("WARNING: ACTIVE COMMUNITY SANCTIONS") end

if hasRestrictions then out :tag('p') :wikitext("The article :, along with other pages relating to "..topic:get('scope')..", is designated by the community as a contentious topic. The current restrictions are:") else out :tag('p') :wikitext(" The use of the contentious topics procedure has been authorised by the community for pages related to ".. topic:get('scope') ..", including this page. " .. (type == 'mini' and ' Editors who repeatedly or seriously fail to adhere to the purpose of Wikipedia, any expected standards of behaviour, or any normal editorial process may be sanctioned.' or '')) end

if not (type == 'mini') then local restrictionList = mw.html.create('ul')

-- 1RR if topic:hasRestriction('1rr') then restrictionList :tag('li') :wikitext("Limit of one revert in 24 hours: This article is under WP:1RR (one revert per editor per article per 24-hour period)") end

-- Text for boilerplate/predefined restrictions if topic:hasRestriction('consensusrequired') then restrictionList :tag('li') :wikitext("Consensus required: All editors must obtain consensus on the talk page of this article before reinstating any edits that have been challenged (via reversion). This includes making edits similar to the ones that have been challenged. If in doubt, do not make the edit.") end

if topic:hasRestriction('brd') then restrictionList :tag('li') :wikitext("24-hr BRD cycle: If a change you make to this article is reverted, you may not reinstate that change unless you discuss the issue on the talk page and wait 24 hours (from the time of the original edit). Partial reverts/reinstatements that reasonably address objections of other editors are preferable to wholesale reverts.") end local customRestrictions = topic:getCustomRestrictions for _, v in ipairs(customRestrictions) do			restrictionList :tag('li') :wikitext(v) end if hasRestrictions then out:node(restrictionList) end

out :tag('p') :wikitext("Editors who repeatedly or seriously fail to adhere to the purpose of Wikipedia, any expected standards of behaviour, or any normal editorial process may be sanctioned.") -- Further info box if hasRestrictions then local furtherInfo = mw.html.create('')

-- Enforcement procedures furtherInfo :tag('p') :wikitext('Enforcement procedures:') :done

local enforcementProcedures = mw.html.create('ul')

if hasRestrictions then enforcementProcedures :tag('li') :wikitext("Violations of any restrictions " .. (hasRevertRestrictions and "(excluding 1RR/reverting violations) " or "") .. "and other conduct issues should be reported to the administrators' incidents noticeboard." .. (hasRevertRestrictions and " Violations of revert restrictions should be reported to the administrators' edit warring noticeboard." or "")) :done :tag('li') :wikitext("Editors who violate any listed restrictions may be blocked by any uninvolved administrator, even on a first offense.") :done else enforcementProcedures :tag('li') :wikitext("Problems should be reported to the administrators' incidents noticeboard.") :done end

enforcementProcedures :tag('li') :wikitext("An editor must be aware before they can be sanctioned.") :allDone furtherInfo:node(enforcementProcedures)

if hasRevertRestrictions then furtherInfo :tag('p') :wikitext("With respect to any reverting restrictions:") :done :tag('ul') :tag('li') :wikitext("Edits made solely to enforce any clearly established consensus are exempt from all edit-warring restrictions. In order to be considered \"clearly established\" the consensus must be proven by prior talk-page discussion.") :done :tag('li') :wikitext("Edits made which remove or otherwise change any material placed by clearly established consensus, without first obtaining consensus to do so, may be treated in the same manner as clear vandalism.") :done :tag('li') :wikitext("Clear vandalism of any origin may be reverted without restriction.") :done :tag('li') :wikitext("Reverts of edits made by anonymous (IP) editors that are not vandalism are exempt from the 1RR but are subject to the usual rules on edit warring. If you are in doubt, contact an administrator for assistance.") :allDone :tag('p') :wikitext("If you are unsure if your edit is appropriate, discuss it here on this talk page first. Remember: When in doubt, don't revert! ") end

local collapsed = frame:expandTemplate{ title = 'collapse', args = { tostring(furtherInfo), ' Remedy instructions and exemptions ', ['bg'] = '#EEE8AA' }}			out :newline :node(collapsed) end -- End further info box end

local box = messageBox.main( 'tmbox', {		type = 'notice',		image = type == 'long' and  or ,		text = frame:preprocess(tostring(out))   })

return box end

-- Builds an alert notice -- -- @param frame -- @param topic topic class instance -- @returns String representation of notice local function buildAlert(frame, topic, sig) local out = mw.html.create('table') :addClass('gs-alert') :cssText("border: 1px solid #AAA; background-color: #E5F8FF; padding: 0.5em; width: 100%; margin-bottom: 1em")

out :tag('tr') :tag('td') :cssText("vertical-align:middle; padding-left:1px; padding-right:0.5em;") :wikitext("") :done :tag('td') :wikitext("This is a standard message to notify contributors about an administrative ruling in effect. It does not imply that there are any issues with your contributions to date.") :newline :wikitext("You have shown interest in ".. topic:get('scope') ..". Due to past disruption in this topic area, the community has authorised uninvolved administrators to impose contentious topics restrictions—such as editing restrictions, bans, or blocks—on editors who do not strictly follow Wikipedia's policies, expected standards of behaviour, or the page-specific restrictions, when making edits related to the topic.") :newline :wikitext("For additional information, please see the guidance on these sanctions. If you have any questions, or any doubts regarding what edits are appropriate, you are welcome to discuss them with me or any other editor." .. (sig and ' '..sig or ''))

return frame:preprocess(tostring(out)) end

-- Builds an edit notice local function buildEditNotice(frame, topic, args) local enHeader = mw.html.create('') local restrictionMsgs = {} if topic:hasRestriction('1rr') then table.insert(restrictionMsgs, "Editors must not make more than one revert per 24 hours (subject to exceptions)") end if topic:hasRestriction('consensusrequired') then table.insert(restrictionMsgs, "Editors must not reinstate any challenged edits (via reversion) without first obtaining consensus on the talk page of this article") end

local customRestrictions = topic:getCustomRestrictions for _, v in ipairs(customRestrictions) do		table.insert(restrictionMsgs, v)	end

if #restrictionMsgs == 0 then return frame:preprocess(syntaxHelp) else local list = mw.html.create('ul') for _,v in ipairs(restrictionMsgs) do			list :tag('li') :wikitext(v) :done end enHeader:wikitext(tostring(list)) end

local enText = mw.html.create('') enText :tag('p') :wikitext(" Breaching the restriction on this page may result in a block or other sanctions. In addition, please note that because this topic area has been disrupted in the past, the community has authorised administrators to take appropriate steps to ensure the smooth running of all pages related to "..topic:get('scope')..". Conduct which does not adhere to our policies and standards of behaviour may be met with sanctions. Please edit carefully.") :done

local editnotice = frame:expandTemplate{ title = 'editnotice', args = { expiry = "indefinite", headerstyle = "font-size: 120%;", style = "background: ivory;", image = "Commons-emblem-issue.svg", imagesize = "50px", header = tostring(enHeader), text = tostring(enText) }}

return editnotice end

--/////////-- -- EXPORTS --/////////-- local p = {}

-- Returns a talk notice -- For documentation, see Template:Gs/talk notice function p.talknotice(frame) local args = getArgs(frame, {		wrappers = {			'Template:Gs/talk notice',			'Template:Gs/talk notice/sandbox'		}	})

local topic = Topic.new(args['topic'] or args[1], args)

if not topic:exists then return frame:preprocess(syntaxHelp) end return buildTalkNotice(frame, topic, args) end

-- Returns an alert -- For documentation, see Template:Gs/alert function p.alert(frame) local args = getArgs(frame, {		wrappers = {			'Template:Gs/alert',			'Template:Gs/alert/sandbox',		}	})

local topic = Topic.new(args['topic'] or args[1], args) if not topic:exists then return frame:preprocess(syntaxHelp) elseif not topic:hasRestriction('ds') then return frame:preprocess(' This topic area is not designated as a contentious topic. Alert is not required. ') end return buildAlert(frame, topic, args['sig']) end

-- Returns an edit notice -- For documentation, see Template:Gs/editnotice function p.editnotice(frame) local args = getArgs(frame, {		wrappers = {			'Template:Gs/editnotice',			'Template:Gs/editnotice/sandbox'		}	})

local topic = Topic.new(args['topic'] or args[1], args) if not topic:exists then return frame:preprocess(syntaxHelp) elseif not topic:hasAnyRestrictions then return frame:preprocess(' Page sanctions are not authorised for this topic area. Edit notice is not required. ') end

return buildEditNotice(frame, topic, args) end

function p.table(frame) local args = getArgs(frame, {		wrappers = {			'Template:Gs/topics/table',			'Template:Gs/topics/table/sandbox',		}	})

local tbl = mw.html.create('table') :addClass('wikitable') :css('font-size', '9pt') :css('background', 'transparent')

-- Headers tbl:tag('tr') :tag('th') :wikitext("Topic code") :done :tag('th') :wikitext("Area of conflict") :done :tag('th') :wikitext("Decision linked to") :allDone -- sort alphabetically local sortedTable = {} for n in pairs(data) do		table.insert(sortedTable, n)	end table.sort(sortedTable)

for _,v in ipairs(sortedTable) do		local sanction = data[v] local title = mw.title.new(sanction.wikilink).redirectTarget			-- probably unnecessarily expensive; just add to config tbl:tag('tr') :tag('td') :wikitext(frame:preprocess("")) :done :tag('td') :wikitext(sanction.scope) :done :tag('td') :wikitext(""..title.fullText.."") :allDone end

return tostring(tbl) end

function p.topicsHelper(frame) local args = getArgs(frame, {		wrappers = {			'Template:Gs/topics',			'Template:Gs/topics/sandbox'		}	})

if args['sanctions scope'] and data[args['sanctions scope']] then return _getTopicData(args['sanctions scope']).scope elseif args['sanctions link'] and data[args['sanctions link']] then return mw.title.new(_getTopicData(args['sanctions link']).wikilink).redirectTarget else return "" -- ? end end

-- Returns true if the given topic name is a valid topic area function p.checkIfValidTopic(topicName) local topic = Topic.new(topicName, nil) if topic:exists then return true else return false end end

return p