Module:Protected edit request/active

require('Module:No globals')

local yesno, makeMessageBox -- passed in from Module:Protected edit request local makeToolbar = require('Module:Toolbar')._main local getPagetype = require('Module:Pagetype')._main local effectiveProtectionLevel = require('Module:Effective protection level')._main

-- -- Helper functions --

local function makeWikilink(page, display) if display then return mw.ustring.format('%s', page, display) else return mw.ustring.format('%s', page) end end

-- -- Title class --

-- This is basically the mw.title class with some extras thrown in.

local title = {} title.__index = title

function title.getProtectionLevelText(protectionLevel) -- Gets the text to use in anchors and urn links. local levels = {unprotected = 'editunprotected', autoconfirmed = 'editsemiprotected', extendedconfirmed = 'editextendedprotected', templateeditor = 'edittemplateprotected', sysop = 'editprotected', interfaceadmin = 'editinterfaceprotected'} return levels[protectionLevel] end

function title.new(...) local success, obj = pcall(mw.title.new, ...) if not (success and obj) then return end title.init(obj) return obj end

function title.init(obj) -- Add a protectionLevel property. obj.protectionLevel = effectiveProtectionLevel(obj.exists and 'edit' or 'create', obj) if obj.protectionLevel == '*' then -- Make unprotected pages return "unprotected". obj.protectionLevel = 'unprotected' elseif obj.protectionLevel == 'user' then -- If we just need to be registered, pretend we need to be autoconfirmed, since it's the closest thing we have. obj.protectionLevel = 'autoconfirmed' end

-- Add a pagetype property. obj.pagetype = getPagetype{page = obj.prefixedText, defaultns = 'all'} -- Add link-making methods. function obj:makeUrlLink(query, display) return mw.ustring.format('[%s %s]', self:fullUrl(query), display) end

function obj:makeViewLink(display) return self:makeUrlLink({redirect = 'no'}, display) end

function obj:makeEditLink(display) return self:makeUrlLink({action = 'edit'}, display) end

function obj:makeHistoryLink(display) return self:makeUrlLink({action = 'history'}, display) end

function obj:makeLastEditLink(display) return self:makeUrlLink({diff = 'cur', oldid = 'prev'}, display) end

function obj:makeWhatLinksHereLink(display) return makeWikilink('Special:WhatLinksHere/' .. self.prefixedText, display) end

function obj:makeCompareLink(otherTitle, display) display = display or 'diff' local comparePagesTitle = title.new('Special:ComparePages') return comparePagesTitle:makeUrlLink({page1 = self.prefixedText, page2 = otherTitle.prefixedText}, display) end

function obj:makeLogLink(logType, display) local logTitle = title.new('Special:Log') return logTitle:makeUrlLink({type = logType, page = self.prefixedText}, display) end

function obj:urlEncode return mw.uri.encode(self.prefixedText, 'WIKI') end

function obj:makeUrnLink(boxProtectionLevel) -- Outputs a urn link. The protection level is taken from the template, rather than detected from page itself, -- as the detection may be inaccurate for cascade-protected and title-blacklisted pages as of Nov 2013. local protectionLinkText = title.getProtectionLevelText(boxProtectionLevel) return mw.ustring.format('[urn:x-wp-%s:%s ]', protectionLinkText, self:urlEncode) end

-- Get a subpage title object, but go through pcall rather than use the unprotected mw.title:subPageTitle. function obj:getSubpageTitle(subpage) return title.new(self.prefixedText .. '/' .. subpage) end

function obj:getSandboxTitle if self.isSubpage and self.contentModel == 'sanitized-css' then local success2, obj2 = pcall(mw.title.makeTitle, self.namespace, self.baseText .. '/sandbox/' .. self.subpageText) if success2 and obj2 then title.init(obj2) return obj2 end end return self:getSubpageTitle('sandbox') end end

-- -- TitleTable class --

local titleTable = {} titleTable.__index = titleTable

function titleTable.new(args) -- Get numerical arguments and make title objects for each of them. local nums = {} for k, v in pairs(args) do		if type(k) == 'number' then table.insert(nums, k)		end end table.sort(nums) local titles = {} for _, num in ipairs(nums) do		local title = title.new(args[num]) table.insert(titles, title) end -- Get the current title, and get the subject title if no titles were specified. titles.currentTitle = mw.title.getCurrentTitle if #titles < 1 then local subjectNs = titles.currentTitle.subjectNsText if subjectNs ~= '' then subjectNs = subjectNs .. ':'		end table.insert(titles, title.new(subjectNs .. titles.currentTitle.text)) end -- Set the metatable. setmetatable(titles, titleTable) return titles end

function titleTable:memoize(memoField, func, ...) if self[memoField] ~= nil then return self[memoField] else self[memoField] = func(...) return self[memoField] end end

function titleTable:titleIterator local i = 0 local n = #self return function i = i + 1 if i <= n then return self[i] end end end

function titleTable:hasSameProperty(memoField, getPropertyFunc) -- If the titles table has more than one title in it, check if they have the same property. -- The property is found using the getPropertyFunc function, which takes a title object as its single argument. local function hasSameProperty(getPropertyFunc) local property for i, obj in ipairs(self) do			if i == 1 then property = getPropertyFunc(obj) elseif getPropertyFunc(obj) ~= property then return false end end return true end

return self:memoize(memoField, hasSameProperty, getPropertyFunc) end

function titleTable:hasSameExistenceStatus -- Returns true if all the titles exist, or if they all don't exist. Returns false if there is a mixture of existence statuses. return self:hasSameProperty('sameExistenceStatus', function (title) return title.exists end) end

function titleTable:hasSameProtectionStatus -- Checks if all the titles have the same protection status (either for creation protection or for edit-protection - the two are not mixed). local sameExistenceStatus = self:hasSameExistenceStatus if sameExistenceStatus then return self:hasSameProperty('sameProtectionStatus', function (title) return title.protectionLevel end) else return sameExistenceStatus end end

function titleTable:hasSamePagetype -- Checks if all the titles have the same pagetype. return self:hasSameProperty('samePagetype', function (title) return title.pagetype end) end

function titleTable:propertyExists(memoField, getPropertyFunc) -- Checks if a title with a certain property exists. -- The property is found using the getPropertyFunc function, which takes a title object as its single argument -- and should return a boolean value. local function propertyExists(getPropertyFunc) for titleObj in self:titleIterator do			if getPropertyFunc(titleObj) then return true end end return false end return self:memoize(memoField, propertyExists, getPropertyFunc) end

function titleTable:hasNonInterfacePage return self:propertyExists('nonInterfacePage', function (titleObj) return titleObj.namespace ~= 8 end) end

function titleTable:hasTemplateOrModule return self:propertyExists('templateOrModule', function (titleObj) return titleObj.namespace == 10 or titleObj.namespace == 828 end) end

function titleTable:hasNonTemplateOrModule return self:propertyExists('nontemplateormodule', function (titleobj) return titleobj.namespace ~= 10 and titleobj.namespace ~= 828 end) end

function titleTable:hasOtherProtectionLevel(level) for titleObj in self:titleIterator do		if titleObj.protectionLevel ~= level then return true end end return false end

function titleTable:getProtectionLevels local function getProtectionLevels local levels = {} for titleObj in self:titleIterator do			local level = titleObj.protectionLevel levels[level] = true end return levels end return self:memoize('protectionLevels', getProtectionLevels) end

-- -- Blurb class definition --

local blurb = {} blurb.__index = blurb

function blurb.new(titleTable, boxProtectionLevel) local obj = {} obj.titles = titleTable obj.boxProtectionLevel = boxProtectionLevel obj.linkCount = 0 -- Counter for the number of total items in the object's link lists. setmetatable(obj, blurb) return obj end

-- Static methods --

function blurb.makeParaText(name, val) local pipe = mw.text.nowiki('|') local equals = mw.text.nowiki('=') val = val and ("" .. val .. "") or '' return mw.ustring.format(' ', pipe, name, equals, val) end

function blurb.makeTemplateLink(s) return mw.ustring.format('%s%s%s', mw.text.nowiki('')) end

function blurb:makeProtectionText local boxProtectionLevel = self.boxProtectionLevel local levels = {['*'] = 'unprotected', autoconfirmed = 'semi-protected', extendedconfirmed = 'extended-confirmed-protected', templateeditor = 'template-protected', sysop = 'fully protected', interfaceadmin = 'interface-protected'} for level, protectionText in pairs(levels) do		if level == boxProtectionLevel then return mw.ustring.format('%s', protectionText) end end error('Unknown protection level ' .. boxProtectionLevel) end

function blurb.getPagetypePlural(title) local pagetype = title.pagetype if pagetype == 'category' then return 'categories' else return pagetype .. 's'	end end

-- Normal methods --

function blurb:makeLinkList(title) local tbargs = {} -- The argument list to pass to Module:Toolbar tbargs.style = 'font-size: smaller;' tbargs.separator = 'dot' -- Page links. table.insert(tbargs, title:makeEditLink('edit')) table.insert(tbargs, title:makeHistoryLink('history')) table.insert(tbargs, title:makeLastEditLink('last')) table.insert(tbargs, title:makeWhatLinksHereLink('links')) -- Sandbox links. local sandboxTitle = title:getSandboxTitle if sandboxTitle and sandboxTitle.exists then table.insert(tbargs, sandboxTitle:makeViewLink('sandbox')) table.insert(tbargs, sandboxTitle:makeEditLink('edit sandbox')) table.insert(tbargs, sandboxTitle:makeHistoryLink('sandbox history')) table.insert(tbargs, sandboxTitle:makeLastEditLink('sandbox last edit')) table.insert(tbargs, title:makeCompareLink(sandboxTitle, 'sandbox diff')) end -- Test cases links. local testcasesTitle = title:getSubpageTitle('testcases') if testcasesTitle and testcasesTitle.exists then table.insert(tbargs, testcasesTitle:makeViewLink('test cases')) end -- Transclusion count link. if title.namespace == 10 or title.namespace == 828 then -- Only add the transclusion count link for templates and modules. local tclink = mw.uri.new{ host = 'templatecount.toolforge.org', path = '/index.php', query = { lang = 'en', name = title.text, namespace = title.namespace, },			fragment = 'bottom' }		tclink = string.format('[%s transclusion count]', tostring(tclink)) table.insert(tbargs, tclink) end -- Protection log link. if title.namespace ~= 8 then -- MediaWiki pages don't have protection log entries. table.insert(tbargs, title:makeLogLink('protect', 'protection log')) end self.linkCount = self.linkCount + #tbargs -- Keep track of the number of total links created by the object. return makeToolbar(tbargs) end

function blurb:makeLinkLists local titles = self.titles if #titles == 1 then return self:makeLinkList(titles[1]) else local ret = {} table.insert(ret, '') for i, titleObj in ipairs(titles) do			table.insert(ret, mw.ustring.format('%s %s', titleObj:makeViewLink(titleObj.prefixedText), self:makeLinkList(titleObj))) end table.insert(ret, '') return table.concat(ret) end end

function blurb:makeIntro local titles = self.titles local requested = 'It is requested that' local protectionText if titles:hasNonInterfacePage then protectionText = ' ' .. self:makeProtectionText else protectionText = '' -- Interface pages cannot be unprotected, so we don't need to explicitly say they are protected. end -- Deal with cases where we are passed multiple titles. if #titles > 1 then local pagetype if titles:hasSamePagetype then pagetype = blurb.getPagetypePlural(titles[1]) else pagetype = 'pages' end return mw.ustring.format("%s edits be made to the following%s %s:", requested, protectionText, pagetype) end -- Deal with cases where we are passed only one title. local title = titles[1] local stringToFormat if title.exists then stringToFormat = '%s an edit be made to the%s %s at %s.' else stringToFormat = '%s the%s %s at %s be created.' end stringToFormat = "'''" .. stringToFormat .. "'''"	return mw.ustring.format(stringToFormat, requested, protectionText, title.pagetype, title:makeViewLink(title.prefixedText)) end

function blurb:makeBody local titles = self.titles local protectionLevels = titles:getProtectionLevels local boxProtectionLevel = self.boxProtectionLevel local hasNonInterfacePage = titles:hasNonInterfacePage local isPlural = false if #titles > 1 then isPlural = true end

local descriptionText = "This template must be followed by a complete and specific description of the request, " if boxProtectionLevel == 'sysop' or boxProtectionLevel == 'templateeditor' then local editText = 'edit' if isPlural then editText = editText .. 's'		end local descriptionCompleteText = mw.ustring.format('so that an editor unfamiliar with the subject matter could complete the requested %s immediately.', editText) descriptionText = descriptionText .. descriptionCompleteText else descriptionText = descriptionText .. 'that is, specify what text should be removed and a verbatim copy of the text that should replace it. '			.. "Please change X" is not acceptable and will be rejected; the request must be of the form "please change X to Y". end

local smallText = '' if boxProtectionLevel == 'sysop' or boxProtectionLevel == 'templateeditor' then local templateFullText if boxProtectionLevel == 'sysop' then templateFullText = 'fully protected' elseif boxProtectionLevel == 'templateeditor' then templateFullText = 'template-protected' end smallText =	'Edit requests to ' .. templateFullText	.. " pages should only be used for edits that are either uncontroversial or supported by consensus." .. " If the proposed edit might be controversial, discuss it on the protected page's talk page before using this template." else local userText local responseTemplate if boxProtectionLevel == 'extendedconfirmed' then userText = 'extended confirmed user' responseTemplate = blurb.makeTemplateLink('EEp') elseif boxProtectionLevel == 'autoconfirmed' then userText = 'autoconfirmed user' responseTemplate = blurb.makeTemplateLink('ESp') elseif boxProtectionLevel == 'interfaceadmin' then userText = 'interface administrator' responseTemplate = blurb.makeTemplateLink('EIp') else userText = 'user' responseTemplate = blurb.makeTemplateLink('ESp') end local answeredPara = blurb.makeParaText('answered', 'no') local stringToFormat =	'The edit may be made by any %s. '			.. Remember to change the %s parameter to "yes" when the request has been accepted, rejected or on hold awaiting user input. .. "This is so that inactive or completed requests don't needlessly fill up the edit requests category. " .. 'You may also wish to use the %s template in the response.' smallText = mw.ustring.format(stringToFormat, userText, answeredPara, responseTemplate) end

if not isPlural then local title = titles[1] if title.namespace == 10 or title.namespace == 828 then local sandboxTitle = title:getSubpageTitle('sandbox') if sandboxTitle and sandboxTitle.exists then smallText = smallText .. ' Consider making changes first to the ' .. sandboxTitle:makeViewLink(title.pagetype .. "'s sandbox") local testcasesTitle = title:getSubpageTitle('testcases') if testcasesTitle and testcasesTitle.exists then smallText = smallText .. ' and ' .. testcasesTitle:makeViewLink('test them thoroughly here') end smallText = smallText .. ' before submitting an edit request.' end end end if hasNonInterfacePage then smallText = smallText .. ' To request that a page be protected or unprotected, make a protection request.' end if boxProtectionLevel == 'sysop' or boxProtectionLevel == 'templateeditor' or boxProtectionLevel == 'interfaceadmin' then smallText = smallText .. ' When the request has been completed or denied, please add the ' .. blurb.makeParaText('answered', 'yes') .. ' parameter to deactivate the template.' end return mw.ustring.format('%s\n\n%s\n ', descriptionText, smallText) end

function blurb:export local intro = self:makeIntro local linkLists = self:makeLinkLists local body = self:makeBody -- Start long links lists on a new line. local linkListSep = ' ' if self.linkCount > 5 then linkListSep = ' '	end return mw.ustring.format('%s%s%s\n\n%s', intro, linkListSep, linkLists, body) end

-- -- Subclass of Module:Protected edit request's box class for active boxes --

local box = {} box.__index = box

function box.new(protectionType, args) -- In the inheritance system used here, an object's metatable is its class, and a class's metatable is its superclass local obj = getmetatable(box).new(protectionType, args) setmetatable(obj, box) local boxProtectionLevels = {semi = 'autoconfirmed', extended = 'extendedconfirmed', template = 'templateeditor', full = 'sysop', interface = 'interfaceadmin'} obj.boxProtectionLevel = boxProtectionLevels[protectionType] obj.demo = yesno(args.demo) -- Set dependent objects. obj.titles = titleTable.new(args) if not yesno(args.force) and obj.titles:hasSameProperty('sameProtectionStatus', function (title) return title.protectionLevel end) and obj.titles[1].protectionLevel ~= 'unprotected' then obj.boxProtectionLevel = obj.titles[1].protectionLevel end obj.blurb = blurb.new(obj.titles, obj.boxProtectionLevel) return obj end

function box:setImage local titles = self.titles local boxProtectionLevel = self.boxProtectionLevel local padlock if boxProtectionLevel == 'sysop' then padlock = 'Full-protection-shackle.svg' elseif boxProtectionLevel == 'interfaceadmin' then padlock = 'Interface-protection-shackle.svg ' elseif boxProtectionLevel == 'templateeditor' then padlock = 'Template-protection-shackle.svg' elseif boxProtectionLevel == 'autoconfirmed' then padlock = 'Semi-protection-shackle.svg' elseif boxProtectionLevel == 'extendedconfirmed' then padlock = 'Extended-protection-shackle.svg' else padlock = 'Padlock-bronze-open.svg' end local stringToFormat = '' local smallPadlock = mw.ustring.format(stringToFormat, padlock, 25) local largePadlock = mw.ustring.format(stringToFormat, padlock, 60) self:setArg('smallimage', smallPadlock) self:setArg('image', largePadlock) end

function box:buildUrnLinks local ret = {} local boxProtectionLevel = self.boxProtectionLevel for titleObj in self.titles:titleIterator do		table.insert(ret, titleObj:makeUrnLink(boxProtectionLevel)) end return mw.ustring.format(' %s ', table.concat(ret)) end

function box:setBlurbText self:setArg('text', self.blurb:export .. self:buildUrnLinks) end

function box:exportRequestTmbox self:setImage self:setBlurbText self:setArg('class', 'editrequest') self:setArg('id', title.getProtectionLevelText(self.boxProtectionLevel)) -- for anchor. yes, this leads to multiple elements with the same ID. we should probably fix this at some point return makeMessageBox('tmbox', self.tmboxArgs) end

function box:exportRequestCategories local cats = {} local boxProtectionLevel = self.boxProtectionLevel local function addCat(cat) table.insert(cats, mw.ustring.format('', cat)) end local protectionCats = { autoconfirmed = 'Wikipedia semi-protected edit requests', extendedconfirmed = 'Wikipedia extended-confirmed-protected edit requests', templateeditor = 'Wikipedia template-protected edit requests', sysop = 'Wikipedia fully-protected edit requests', interfaceadmin = 'Wikipedia interface-protected edit requests' }	addCat(protectionCats[boxProtectionLevel]) if self.titles:hasOtherProtectionLevel(boxProtectionLevel) then addCat('Wikipedia edit requests possibly using incorrect templates') end return table.concat(cats) end

function box:export if not self.titles.currentTitle.isTalkPage and not self.demo then return ' Error: Protected edit requests can only be made on the talk page. '	end local ret = {} table.insert(ret, self:exportRequestTmbox) if not self.demo then table.insert(ret, self:exportRequestCategories) end return table.concat(ret) end

-- -- Function exported to Module:Protected edit request --

return function(superclass, yn, mb) yesno = yn	makeMessageBox = mb	return setmetatable(box, superclass) end