Module:Requested move: Difference between revisions

Content deleted Content added
finish adding support for Template:Requested move
m simplify some code (should be a no-op; if there are any errors no discussion is needed before reverting (WP:TPEDISPUTE))
 
(60 intermediate revisions by 10 users not shown)
Line 1:
-- This module implements {{requested move-multi}}.
 
-- Load necessary modules
local getArgs = require('Module:Arguments').getArgs
local tableTools = require('Module:TableTools')
local yesno = require('Module:Yesno')
local mRedirect = require('Module:Redirect')
 
-- Set static values
local defaultNewPagename = '?' -- Name of new pages that haven't been specified
 
local p = {}
Line 10 ⟶ 15:
-- Helper functions
--------------------------------------------------------------------------------
 
local function err(msg, numargs, reason, count)
-- Generates a wikitext error message
local commented = '<!-- {{subst:requested move|'
return string.format('{{error|%s}}', msg)
if count ~= 1 then
commented = commented .. 'new1='
end
commented = commented .. numargs[1]['new']
for i = 2,count do
commented = commented .. string.format('|current%i=%s', i, (numargs[i]['current'] or ''))
commented = commented .. string.format('|new%i=%s', i, (numargs[i]['new'] or ''))
end
if reason then
commented = commented .. '|reason=' .. reason
end
commented = commented .. '}} -->'
return string.format('{{error|%s}}', msg) .. commented
end
 
local function validateNewParamvalidateTitle(newpage, numparamName, noredir,paramNum) multi)
-- Validates thea parameterspage new1name, new2and if it is valid, etc.returns true and the title
-- object for that page. If it is not valid, returns false and the
-- The checks "NewName" and "New title for page n" are for users who copy
-- appropriate error message.
-- the template invocation from the documentation page without changing it.
 
if not new or (
-- Check for a small subset of characters that cannot be used in MediaWiki
multi and new == 'New title for page ' .. tostring(num)
-- titles. For the full set of restrictions, see
or not multi and new == 'NewName'
-- [[Wikipedia:Page name#Technical restrictions and limitations]]. This is
)
-- also covered by the invalid title check, but with this check we can give
then
-- a more specific error message.
return '?'
local invalidChar = page:match('[#<>%[%]|{}]')
elseif noredir then
if invalidChar then
return '{{no redirect|' .. new .. '}}'
local msg = 'Invalid character "'
else
.. invalidChar
return new
.. '" found in the "'
.. paramName
.. paramNum
.. '" parameter'
return false, msg
end
 
-- Get the title object. This also checks for invalid titles that aren't
-- covered by the previous check.
local titleObj = mw.title.new(page)
if not titleObj then
local msg = 'Invalid title detected in parameter "'
.. paramName
.. paramNum
.. '"; check for [[Wikipedia:Page name#'
.. 'Technical restrictions and limitations|invalid characters]]'
return false, msg
end
 
-- Check for interwiki links. Titles with interwikis make valid title
-- objects, but cannot be created on the local wiki.
local interwiki = titleObj.interwiki
if interwiki and interwiki ~= '' then
local msg = 'Invalid title detected in parameter "'
.. paramName
.. paramNum
.. '"; has [[Help:Interwiki linking|interwiki prefix]] "'
.. titleObj.interwiki
.. ':"'
return false, msg
end
 
return true, titleObj
end
 
--------------------------------------------------------------------------------
local function generateLinkListItem(current, new, num, multi)
-- Validate title entry point (used at [[Template:RMassist/core]])
-- Generates a list item for pages to be moved.
--------------------------------------------------------------------------------
-- For single the item doesn't have a bullet, as it will be the only page
function p.validateTitle(frame)
-- in the list.
local itemvalue = stringframe.format(args[1]
local validTitle, currentTitle = validateTitle(value or '', '1', '')
'\n%s[[:%s]] → %s',
if not validTitle then
multi and '* ' or '',
-- If invalid, the second parameter is the error message.
current,
return currentTitle
validateNewParam(new, num, true, multi)
)end
return item'yes'
end
 
--------------------------------------------------------------------------------
-- Confirm protection levels (used at [[Template:Requested move/dated]])
-- Entry points
--------------------------------------------------------------------------------
 
function p.singleprotected(frame)
returnlocal p.mainargs = getArgs(frame, false{parentOnly = true})
if args.protected then
end
local levels = mw.title.new(args.protected).protectionLevels
 
local levelMove = levels['move'] and levels['move'][1]
function p.multi(frame)
local levelEdit = levels['edit'] and levels['edit'][1]
return p.main(frame, true)
local levelCreate = levels['create'] and levels['create'][1]
if levelMove == 'sysop'
or levelEdit == 'sysop'
or levelEdit == 'editprotected'
or levelCreate == 'sysop' then
return 'sysop'
elseif levelMove == 'templateeditor'
or levelEdit == 'templateeditor'
or levelCreate == 'templateeditor' then
return 'templateeditor'
end
end
end
 
Line 62 ⟶ 124:
--------------------------------------------------------------------------------
 
function p.main(frame, multi)
-- Initialise variables
local args = getArgs(frame)
local new1 = args.new1 or args[1] -- Holds the first proposed new title
local nums, numCount -- Used to iterate over arguments for multi
local title = mw.title.getCurrentTitle()
local subjectTitle = title.subjectPageTitle
local subjectText = subjectTitle.prefixedText
 
----------------------------------------------------------------------------
-- Initialise variables and preprocess the arguments
-- Error checks
----------------------------------------------------------------------------
local args = getArgs(frame, {parentOnly = true})
-- Find the template name for the error messages.
local title = mw.title.getCurrentTitle()
local template
 
--[[
-- To iterate over the current1, new1, current2, new2, ... arguments
-- we get an array of tables sorted by number and compressed so that
-- it can be traversed with ipairs. The table format looks like this:
-- {
-- {current = x, new = y, num = 1},
-- {current = z, new = q, num = 2},
-- ...
-- }
-- The "num" field is used to correctly preserve the number of the parameter
-- that was used, in case users skip any numbers in the invocation.
--
-- The current1 parameter is a special case, as it does not need to be
-- specified. To avoid clashes with later current parameters, we need to
-- add it to the args table manually.
--
-- Also, we allow the first positional parameter to be an alias for the
-- new1 parameter, so that the syntax for the old templates
-- {{requested move}} and {{move-multi}} will both be supported.
--
-- The "multi" variable tracks whether we are using the syntax previously
-- produced by {{requested move}}, or the syntax previously produced by
-- {{move-multi}}. For the former, multi is false, and for the latter it is
-- true.
--]]
if not args.current1 then
args.current1 = title.subjectPageTitle.prefixedText
end
 
-- Find the first new page title, if specified, and keep a record of the
-- prefix used to make it; the prefix will be used later to make error
-- messages.
local firstNewParam
if args.new1 then
firstNewParamPrefix = 'new'
elseif args[1] then
args.new1 = args[1]
firstNewParamPrefix = ''
else
firstNewParamPrefix = ''
end
 
-- Build the sorted argument table.
local argsByNum = {}
for k, v in pairs(args) do
k = tostring(k)
local prefix, num = k:match('^(%l*)([1-9][0-9]*)$')
if prefix == 'current' or prefix == 'new' then
num = tonumber(num)
local subtable = argsByNum[num] or {}
subtable[prefix] = v
subtable.num = num
argsByNum[num] = subtable
end
end
argsByNum = tableTools.compressSparseArray(argsByNum)
 
-- Calculate the number of arguments and whether we are dealing with a
-- multiple nomination.
local argsByNumCount = #argsByNum
local multi = (argsByNumCount >= 2)
 
--[[
-- Validate new params.
-- This check ensures we don't have any absent new parameters, and that
-- users haven't simply copied in the values from the documentation page.
--]]
if multi then
for i, t in ipairs(argsByNum) do
template = 'Move-multi'
local new = t.new
local num = t.num
if not new or new == 'New title for page ' .. tostring(num) then
argsByNum[i].new = defaultNewPagename
end
end
else
local new = argsByNum[1].new
template = 'Requested move'
if not new or new == 'NewName' then
argsByNum[1].new = defaultNewPagename
end
end
----------------------------------------------------------------------------
-- Error checks
----------------------------------------------------------------------------
-- Subst check
if not mw.isSubsting() then
local lang = mw.language.getContentLanguage()
local tplcfirst = lang:lcfirst(template)
local lb = mw.text.nowiki('{{')
local rb = mw.text.nowiki('}}')
local msg = '<strong class="error">'
.. 'This template must be [[Wikipedia:Template substitution|substituted]];'
.. ' replace %s%ssrequested move%s with %ssubst:%srequested move%s'
.. '</strong>'
msg = string.format(msg, lb, tplcfirst, rb, lb, tplcfirst, rb)
return msg
end
-- Check we are on a talk page
-- Namespace checks
local namespace = title.namespace
local subjectSpace = mw.site.namespaces[namespace].subject.id
if not title.isTalkPage then
local msg = '[[Template:%sRequested move]] must be used in a TALKSPACE, e.g., [[%s:%s]]'
msg = string.format(msg, tempate, mw.site.namespaces[title.namespace].talk.name, title.text)
return err(msg, argsByNum, args.reason, argsByNumCount)
elseif subjectSpace == 14 then -- Category
local msg = '[[Template:' .. template .. ']] is not for categories,'
.. ' see [[Wikipedia:Categories for discussion]]'
return err(msg)
elseif subjectSpace == 6 then -- File
local msg = '[[Template:' .. template .. ']] is not for files;'
.. ' see [[Wikipedia:Moving a page#Moving a file page]]'
.. ' (use [[Template:Rename media]] instead)'
return err(msg)
elseif subjectSpace == 2 then -- User
local msg = '[[Template:' .. template .. ']] is not for moves from user space;'
.. ' see [[Wikipedia:Articles for creation]]'
.. ' (use '
.. mw.text.nowiki('{{')
.. '[[Wikipedia:Substitution|subst]]:[[Template:Submit|submit]]'
.. mw.text.nowiki('}}')
.. ' instead),'
.. ' or [[Help:How to move a page|move it yourself]]'
return err(msg)
end
-- Check the subject page existsarguments
local currentDupes, newDupes = {}, {}
if not subjectTitle.exists then
for i, t in ipairs(argsByNum) do
local msg = 'Must create [['
local current = t.current
.. subjectText
local new = t.new
.. ']] before requesting that it be moved'
local num = t.num
return err(msg)
local validCurrent
local currentTitle
local subjectSpace
 
-- Check for invalid or missing currentn parameters
-- This check must come first, as mw.title.new will give an error if
-- it is given invalid input.
if not current then
local msg = '"current%d" parameter missing;'
.. ' please add it or remove the "new%d" parameter'
msg = string.format(msg, num, num)
return err(msg, argsByNum, args.reason, argsByNumCount)
end
 
-- Get the currentn title object, and check for invalid titles. This check
-- must come before the namespace and existence checks, as they will
-- produce script errors if the title object doesn't exist.
validCurrent, currentTitle = validateTitle(current, 'current', num)
if not validCurrent then
-- If invalid, the second parameter is the error message.
local msg = currentTitle
return err(msg, argsByNum, args.reason, argsByNumCount)
end
 
-- Category namespace check
subjectSpace = mw.site.namespaces[currentTitle.namespace].subject.id
if subjectSpace == 14 then
local msg = '[[Template:Requested move]] is not for categories,'
.. ' see [[Wikipedia:Categories for discussion]]'
return err(msg, argsByNum, args.reason, argsByNumCount)
-- File namespace check
elseif subjectSpace == 6 then
local msg = '[[Template:Requested move]] is not for files;'
.. ' see [[Wikipedia:Moving a page#Moving a file page]]'
.. ' (use [[Template:Rename media]] instead)'
return err(msg, argsByNum, args.reason, argsByNumCount)
 
-- Draft and User namespace check
elseif subjectSpace == 2 or subjectSpace == 118 then
local msg = '[[Template:Requested move]] is not for moves from draft or user space.'
.. '<br>If you would like to submit your draft for review, add <code>{{tlf|subst:submit}}</code>'
.. 'to the top of the page.'
.. '<br>Otherwise, see [[Help:How to move a page]] for instructions.'
.. '<br>If you cannot move it yourself, see [[Wikipedia:Requested moves#Requesting technical moves|Requesting technical moves]].'
return err(msg, argsByNum, args.reason, argsByNumCount)
end
 
-- Request to move a single page must be placed on that page's talk, or the page it redirects to
if not multi and args.current1 ~= title.subjectPageTitle.prefixedText then
local idealpage = mw.title.new(args.current1).talkPageTitle
local rtarget = mRedirect.getTarget(idealpage)
if rtarget == title.prefixedText then
multi = true
else
local msg = 'Request to move a single page must be placed on that page\'s talk or the page its talk redirects to'
return err(msg, argsByNum, args.reason, argsByNumCount)
end
end
 
-- Check for non-existent titles.
if not currentTitle.exists then
local msg = 'Must create [[:%s]] before requesting that it be moved'
msg = string.format(msg, current)
return err(msg, argsByNum, args.reason, argsByNumCount)
end
 
-- Check for duplicate current titles
-- We know the id isn't zero because we have already checked for
-- existence.
local currentId = currentTitle.id
if currentDupes[currentId] then
local msg = 'Duplicate title detected ("'
.. currentTitle.prefixedText
.. '"); cannot move the same page to two different places'
return err(msg, argsByNum, args.reason, argsByNumCount)
else
currentDupes[currentId] = true
end
 
-- Check for invalid new titles. This check must come before the
-- duplicate title check for new titles, as it will produce a script
-- error if the title object doesn't exist.
local validNew, newTitle = validateTitle(
new,
multi and 'new' or firstNewParamPrefix,
num
)
if not validNew then
-- If invalid, the second parameter is the error message.
local msg = newTitle
return err(msg, argsByNum, args.reason, argsByNumCount)
end
 
-- Check for duplicate new titles.
-- We can't use the page_id, as new pages might not exist, and therefore
-- multiple pages may have an id of 0. Use the prefixedText as a
-- reasonable fallback. We also need to check that we aren't using the
-- default new page name, as we don't want it to be treated as a duplicate
-- page if more than one new page name has been omitted.
local newPrefixedText = newTitle.prefixedText
if newPrefixedText ~= defaultNewPagename then
if newDupes[newPrefixedText] then
local msg = 'Duplicate title detected ("'
.. newTitle.prefixedText
.. '"); cannot move two different pages to the same place'
return err(msg, argsByNum, args.reason, argsByNumCount)
else
newDupes[newPrefixedText] = true
end
end
end
----------------------------------------------------------------------------
-- Check for page protection
----------------------------------------------------------------------------
local highestProtection = ''
local protectedTitle = ''
-- Checking page protection requires use of .protectionLevels(), one of the
-- "expensive" parser functions, which stop working after 500 uses total.
-- Without some limit set, this starts breaking near 250 distinct titles.
local titleLimit = 80
local titlesChecked = 0
local titles = {}
-- Consolidate duplicate titles (i.e., when moving A to B and B to C)
for i = 1,argsByNumCount do
titles[mw.title.new(argsByNum[i]['current'])] = true
titles[mw.title.new(argsByNum[i]['new'])] = true
end
-- Check each title t, while ignoring the "true" value
for t, _ in pairs(titles) do
if titlesChecked < titleLimit then
local levels = t.protectionLevels
titlesChecked = titlesChecked + 1
local levelMove = levels['move'] and levels['move'][1]
local levelEdit = levels['edit'] and levels['edit'][1]
local levelCreate = levels['create'] and levels['create'][1]
if levelMove == 'sysop'
or levelEdit == 'sysop'
or levelEdit == 'editprotected'
or levelCreate == 'sysop' then
highestProtection = 'sysop'
protectedTitle = tostring(t)
break
elseif levelMove == 'templateeditor'
or levelEdit == 'templateeditor'
or levelCreate == 'templateeditor' then
highestProtection = 'templateeditor'
protectedTitle = tostring(t)
end
else
-- End the "for" loop if the titleLimit is reached
break
end
end
 
----------------------------------------------------------------------------
-- Generate the heading
-- Build the {{requested move/dated}} invocation.
----------------------------------------------------------------------------
 
-- For custom values of |heading=, use those.
-- For |heading=no, |heading=n, etc., don't include a heading.
-- Otherwise use the current date as a heading.
local heading = args.heading or args.header
local useHeading = yesno(heading, heading)
if heading and useHeading == heading then
heading = '== ' .. heading .. ' ==\n\n'
elseif useHeading == false then
heading = ''
else
local lang = mw.language.getContentLanguage()
local headingDate = lang:formatDate('j F Y')
heading = '== Requested move ' .. headingDate .. ' ==\n\n'
end
----------------------------------------------------------------------------
-- Build the {{requested move/dated}} invocation
----------------------------------------------------------------------------
 
Line 140 ⟶ 427:
rmd[#rmd + 1] = '{{requested move/dated'
 
-- Setup for multi
if multi then
--[[
-- To iterate over the current1, current2, ... arguments
-- we get an array of their numbers (the "nums" table). So if we were
-- passed the arguments current1, current2 and current5, the nums table
-- would contain {1, 2, 5}.
--
-- The current1 parameter is a special case, as it does not need to be
-- specified. For the numbering to work properly, we need to add it to
-- the args table manually. Although it does not need to be specified,
-- we don't want the numbering to change if a user specifies it
-- anyway.
--]]
args.current1 = subjectText
nums = tableTools.affixNums(args, 'current')
numCount = #nums
 
-- Add initial parameters specific to multi
rmd[#rmd + 1] = '|multiple=yes'
rmd[#rmd + 1] = '\n|current1=' .. subjectTextargsByNum[1].current
end
 
Line 168 ⟶ 437:
-- positional parameter, and for multi the parameter name is "new1".
--]]
local new1rmd = validateNewParam(new1, 1, false, multi)
local new1param = multi and 'new1=' or ''
rmd[#rmd + 1] = '|' .. new1param .. new1rmdargsByNum[1].new
 
-- Add the rest of themore arguments for multi.
if multi and numCount >= 2 then
for i = 2, numCountargsByNumCount do
local numt = numsargsByNum[i]
local numString = tostring(numi)
local current = args['t.current' .. numString]
local new = args['t.new' .. numString]
new = validateNewParam(new, num, false, true)
rmd[#rmd + 1] = '|current' .. numString .. '=' .. current
rmd[#rmd + 1] = '|new' .. numString .. '=' .. new
end
end
-- Highest page protection (if admin or template-editor)
if highestProtection == 'sysop' or highestProtection == 'templateeditor' then
rmd[#rmd + 1] = '|protected=' .. protectedTitle
end
 
-- Pass through demo=yes to the
if args.demo ~= nil then
rmd[#rmd + 1] = '|demo='
rmd[#rmd + 1] = args.demo
end
-- The old multi template always has a bar before the closing curly
-- braces, so we will do that too.
Line 190 ⟶ 468:
rmd[#rmd + 1] = '|'
end
 
rmd[#rmd + 1] = '}}'
rmd = table.concat(rmd)
Line 198 ⟶ 476:
----------------------------------------------------------------------------
 
local linkList = {}
for i, t in ipairs(argsByNum) do
if multi then
local current = t.current
linkList = {}
local new = t.new
-- The first list item is a special case.
local msg = '\n%s[[:%s]] → '
linkList[#linkList + 1] = generateLinkListItem(subjectText, new1, 1, true)
if numCountnew >~= 2defaultNewPagename then
msg = msg .. '{{no redirect|%s}}'
for i = 2, numCount do
else
local num = nums[i]
msg = msg .. '%s'
local numString = tostring(num)
local current = args['current' .. numString]
local new = args['new' .. numString]
linkList[#linkList + 1] = generateLinkListItem(current, new, num, true)
end
end
local item = string.format(
linkList = table.concat(linkList)
msg,
else
multi and '* ' or '', -- Don't make a list for single page moves.
linkList = generateLinkListItem(subjectText, new1, 1, false)
current,
new
)
linkList[#linkList + 1] = item
end
linkList = table.concat(linkList)
 
----------------------------------------------------------------------------
Line 222 ⟶ 501:
 
-- Reason
local reason = args.reason or args[2] or 'Please putplace your reasonrationale for movingthe proposed move here.'
reason = '– ' .. reason .. ' ~~~~'
if yesno(args.sign or args.sig or args.signature or 'unspecified', not reason:match("~~~$")) then
reason = reason .. ' ~~~~'
end
 
-- Talk blurb
local talk = ''
if yesno(args.talk, true) then
talk = frame:expandTemplate{title = 'Requested move/talk'}
else
talk = ''
end
 
----------------------------------------------------------------------------
-- Assemble the output. The old templates start with a line break,
-- Assemble the output
-- so we will do that too.
----------------------------------------------------------------------------
local ret = '\n' .. rmd .. '\n' .. linkList .. '\n' .. reason .. talk
 
-- The old templates start with a line break, so we will do that too.
local ret = string.format(
'\n%s%s\n%s%s%s%s',
heading,
rmd,
linkList,
multi and '\n' or ' ',
reason,
talk
)
return ret
end