Module:POTY

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
Lua

CodeDiscussionEditHistoryLinksLink count Subpages:DocumentationTestsResultsSandboxLive code All modules


Usage

{{#invoke:POTY|function_name}}

Code

require('strict')

local p = {}
p.i18n = require('Module:POTY/i18n')

local msg, langModuleInit
local lang, userLang
local messagePrefix = 'com-poty-'
local function initLangModule()
    --! From [[:de:Modul:Expr]]; by [[:de:User:PerfektesChaos]]; 
    --! Derivative work: Rillke
    
    if langModuleInit then return end
    langModuleInit = true
    userLang = mw.getCurrentFrame():callParserFunction('int', 'lang')
    lang = mw.language.new(userLang)
 
    msg = function( key, ... )
        -- Retrieve localized message string in content language
        -- Precondition:
        --     key  -- string; message ID
        -- Postcondition:
        --     Return some message string
        -- Uses:
        --     >  messagePrefix
        --     >  p.i18n
        --     >  userLang
        --     mw.message.new()
        local m = mw.message.new( messagePrefix .. key, unpack(arg) )
        local r = false
        if m:isBlank() then
            r = p.i18n[ key ]
            -- reverse order, think of $12
            if r then
	            local arglen = #arg
	            for i = arglen, 1, -1 do
	            	r = r:gsub('$' .. tostring(i), arg[i])
	            end
	        end
        else
            m:inLanguage( userLang )
            r = m:plain()
        end
        if not r then
            r = '(((Error in [[Module:POTY]]. Message not found: '.. key .. ')))'
        end
        return r
    end -- msg()
 
    local langIsInit = true
end

local function deepmerge(t1, t2)
	for k,v in pairs(t2) do
		if type(v) == 'table' then
			v = deepmerge(t1[k] or {}, v)
		end
		t1[k] = v
	end
	return t1
end

local function pad(inp, length, fill)
	while #inp < length do
		inp = fill .. inp
	end
	return inp
end

local function zero212(n)
	if n == 0 then return 12 end
	return n
end

local function getTableKey(t, val)
	for i, v in pairs(t) do
		if v == val then return i end
	end
end

local function getNestedTableKey(t, val)
	for k, v in pairs(t) do
		local ret = getTableKey(v, val)
		if ret then
			return k
		end
	end
end

local function i18nTable(frame, i18nSource, pefix)
	local langlist = mw.language.fetchLanguageNames()
	local txt1 ='# First, select the language you would like to translate into from the languages-table. Your current choice is <b>{{#language:{{int:lang}}}}</b>.\n'
	local txt2 = '# You may use the find-functionality of your browser ({{key press|Ctrl|F}}) for quickly searching your language.\n'
	local txt3 = '# Click the appropriate link in the <i>contribute</i>-column of the messages-table.\n'
	local ret = txt1 .. txt2 .. txt3 .. '\n<h2>Languages</h2>\n<div class="plainlinks">'
	local langcodes = {}
	local langtable = {}
	local msgtable = {}
	
	for lcode, lname in pairs(langlist) do
		table.insert(langcodes, lcode)
	end
	table.sort(langcodes)
	for _, lcode in ipairs(langcodes) do
		table.insert(langtable, '[{{fullurl:{{FULLPAGENAME}}|uselang=' .. lcode .. '}} <span style="white-space:nowrap">' .. langlist[lcode] .. '</span>]')
	end
	
	for key, val in pairs(i18nSource) do
		local msgFull = pefix .. key
		local js = key:find('.js/', 1, true)
		if js then
			table.insert(msgtable, '<tr><td>[[MediaWiki:' .. msgFull .. '/en.js]]</td><td>{{#tag:syntaxhighlight|{{MediaWiki:' .. msgFull .. '/en.js}}|lang=JavaScript}}</td>' ..
				'<td>{{#tag:syntaxhighlight|{{MediaWiki:' .. msgFull .. '/' .. userLang .. '.js}}|lang=JavaScript}}</td><td>[{{fullurl:MediaWiki:' .. msgFull .. '/' .. userLang .. '.js|action=edit&preload={{urlencode:MediaWiki:' .. msgFull .. '/en.js|WIKI}}}} Create] • ' ..
				'[{{fullurl:MediaWiki talk:' .. msgFull .. '/' .. userLang .. '.js|action=edit&section=new&preloadtitle=Translation%20edit%20request%20for%20POTY&editintro=Commons:Picture_of_the_Year/mw-message-edit-request&preload={{urlencode:MediaWiki:' .. msgFull .. '/en.js|WIKI}}}} Request edit]</td></tr>')
		else
			table.insert(msgtable, '<tr><td>[[MediaWiki:' .. msgFull .. ']]</td><td>{{msgnw:MediaWiki:' .. msgFull .. '}}</td>' ..
				'<td>{{int:' .. msgFull .. '}}' .. '\n' .. '</td><td>[{{fullurl:MediaWiki:' .. msgFull .. '/' .. userLang .. '|action=edit&preload={{urlencode:MediaWiki:' .. msgFull .. '|WIKI}}}} Create] • ' ..
				'[{{fullurl:MediaWiki talk:' .. msgFull .. '/' .. userLang .. '|action=edit&section=new&preloadtitle=Translation%20edit%20request%20for%20POTY&editintro=Commons:Picture_of_the_Year/mw-message-edit-request&preload={{urlencode:MediaWiki:' .. msgFull .. '|WIKI}}}} Request edit]</td></tr>')
		end
	end
	
	ret = frame:preprocess(ret .. table.concat(langtable, ' • ') .. '</div>\n' ..
		'<h2>Messages</h2>\n<table class="wikitable sortable"><tr><th>Message</th><th>Source code</th><th>In your chosen language</th><th>Contribute</th></tr>' ..  table.concat(msgtable, '\n') .. '</table>')
	return ret
end

function p.i18nTable(frame)
	initLangModule()
	
	local i18n, prefix
	if frame.args['misc'] then
		i18n = require('Module:POTY/i18n/misc')
		prefix = ''
	else
		i18n = p.i18n
		prefix = 'com-poty-'
	end
	return i18nTable(frame, i18n, prefix)
end

function p.galleryOverview(frame)
	initLangModule()
	
	local title = frame:getParent():getTitle()
	local titlesplit = mw.text.split(title, '/', true)
	local year = titlesplit[2]
	local gallerybase = table.concat(titlesplit, '/')
	local defaults = require('Module:POTY/config')
	local yconfig = require('Module:POTY/config/' .. year)
	local parser = require('Module:POTY/parser')
	local settings = {}


	-- Merge specific into the default settings
	deepmerge(settings, defaults)
	deepmerge(settings, yconfig)

	if not settings.candidateListing or 0 == #settings.candidateListing then
		return msg('no-candidate-listing', 'Module:POTY/config/' .. year)
	end
	local candidates, headings = parser.parse(settings.candidateListing)
	
	local thStyle = 'background:' .. (settings.stateColorTemplate or ('{{POTY' .. year .. '/state/color}}'))
	
	local topicGalleryTable = ''
	local topicGalleryMap = {}
	local topicGalleryList = {}
	local topicGalleryOut = {}
	local monthGalleryTable = ''
	local monthGalleryTableMap = {}
	local allTable = ''
	local indexTable = ''
	local monthACount = 0
	local monthBCount = 0
	local h2Style = settings.galleryHeadingStyle .. '; margin-bottom:.22em'
	local subHeader = '<tr><td style="' .. (settings.stateColorTemplate3 or ('{{POTY' .. year .. '/state/color3}}')) .. '">&nbsp;</td></tr>'
	local allTableFooter = subHeader:gsub('&nbsp;', msg('no-voting-on-these-pages'))
	
	-- Topic table
	for i, v in ipairs(headings) do
		local tk = getNestedTableKey( settings.groups, v.hId:lower() )
		if not tk then
			return '<span class="error">Fatal: There is a heading id=<code>' .. v.hId .. '</code> which isn\'t known to [[Module:POTY/config]]. Please make sure it\'s listed under <code>groups=</code>.</span>'
		end
		if not topicGalleryMap[tk] then
			topicGalleryMap[tk] = {}
			table.insert( topicGalleryList, tk )
		end
		table.insert( topicGalleryMap[tk], v )
	end
	for i, v in ipairs(topicGalleryList) do
		local currentTopic = topicGalleryMap[v]
		for _, g in ipairs(currentTopic) do
			local hId = g.hId:lower()
			local f = settings.galleryIcons[hId]
			local cat = msg('cat-' .. hId)
			local tdG = '<td style="padding:.2em 1em">[[File:' .. (f or '1x1.png') .. '|25px|' .. cat .. '|link=' .. gallerybase .. '/' .. ']] [[' .. gallerybase .. '/' .. g.hId .. '|' .. cat .. ']] (' .. #candidates[g.hId] .. ')</td>'
			if v then
				table.insert( topicGalleryOut, '<tr><th rowspan="' .. #currentTopic .. '">' .. msg('group-' .. v) .. '</th>' .. tdG .. '</tr>' )
				v = nil
			else
				table.insert( topicGalleryOut, '<tr>' .. tdG .. '</tr>')
			end
		end
	end
	
	topicGalleryTable = '<table style="width:90%" class="wikitable"><tr><th colspan="2" style="' .. thStyle ..
		'"><h2 style="' .. h2Style .. '">' .. msg('galleries-by-categories') .. '</h2></th></tr><tr><th style="' .. (settings.stateColorTemplate3 or ('{{POTY' .. year .. '/state/color3}}')) .. '">' .. lang:ucfirst(msg('genre')) .. '</th><th style="' .. (settings.stateColorTemplate3 or ('{{POTY' .. year .. '/state/color3}}')) .. '">' .. lang:ucfirst(msg('categories')) .. '</th></tr>' .. 
		table.concat(topicGalleryOut, '\n') ..
		'<tr><td colspan="2" style="font-size: x-small; ' .. (settings.stateColorTemplate3 or ('{{POTY' .. year .. '/state/color3}}')) .. '">[[' .. (settings.aboutIconPage or  'Special:MyLanguage/Commons:Picture of the Year/' .. year .. '/About Icons') .. '|<span style="' .. (settings.stateColorTemplate3 or ('{{POTY' .. year .. '/state/color3}}')) .. '">' .. msg('about-icons') .. '</span>]]</td></tr>' ..
		'</table>'
	
	-- Month table
	local candidateList = {}
	for hId, group in pairs(candidates) do
		for i, file in ipairs(group) do
			table.insert( candidateList, file )
		end
	end
	table.sort( candidateList, function(a, b) return (a.fpId or '') < (b.fpId or '') end )
	for i = 1, 12, 1 do
		local month = pad(tostring(i), 2, '0')
		local counter = 0
		for x, file in ipairs(candidateList) do
			local fpId = year .. '-' .. month
			if (file.fpId or ''):find(fpId, 1, true) then
				counter = counter + 1
				if i < 7 then
					monthACount = monthACount + 1
				else
					monthBCount = monthBCount + 1
				end
			end
		end
		
		local gLink = gallerybase .. '/M' .. pad(tostring(i), 2, '0')
		local gDesc = '{{lang|tag=span|{{int:lang}}|{{int:' .. settings.months[i] .. '}}}}'
		table.insert( monthGalleryTableMap, '<tr><td style="padding:.2em 1em">' .. 
			'[[File:' .. settings.monthIcons[i] .. '|25px|' .. gDesc .. '|link=' .. gLink .. ']] [[' .. gLink .. '|'.. gDesc ..']]\t(' .. tostring(counter) .. ')</td></tr>' )
	end
	
	monthGalleryTable = '<table style="min-width:70%" class="wikitable"><tr><th style="' .. thStyle ..
		'"><h2 style="' .. h2Style .. '">' .. msg('fps-by-month', year) .. '</h2></th></tr>' ..
		subHeader ..
		table.concat(monthGalleryTableMap, '\n') ..
		subHeader ..
		'</table>'
		
		
	allTable = '<table style="min-width:70%" class="wikitable"><tr><th style="' .. thStyle ..
		'"><h2 style="' .. h2Style .. '">' .. msg('all-or-half-year', year) .. '</h2></th></tr>' .. 
		subHeader ..
		'<tr><td style="padding:.2em 1em">[[' .. gallerybase .. '/' .. year .. '-A' .. '|' .. msg('from-to', '{{int:' .. settings.months[1] .. '}}', '{{int:' .. settings.months[6] .. '}}', year, tostring(monthACount)) .. ']]</td></tr>' ..
		'<tr><td style="padding:.2em 1em">[[' .. gallerybase .. '/' .. year .. '-B' .. '|' .. msg('from-to', '{{int:' .. settings.months[7] .. '}}', '{{int:' .. settings.months[12] .. '}}', year, tostring(monthBCount)) .. ']]</td></tr>' ..
		'<tr><td style="padding:.2em 1em">[[' .. gallerybase .. '/ALL' .. '|' .. msg('all-year-fp', year, tostring(monthACount + monthBCount)) .. ']]</td></tr>' ..
		allTableFooter ..
		'</table>'
	
	return frame:preprocess(
		'<div dir="{{dir|{{int:lang}}}}" lang="{{int:lang}}">' ..
			'<div style="float:{{dir|{{int:lang}}|right|left}}; width:65%;">' .. topicGalleryTable .. '</div><div style="float:{{dir|{{int:lang}}|right|left}}; width:33%;">' .. monthGalleryTable .. '</div><div style="float:{{dir|{{int:lang}}|right|left}}; width:33%;">' .. allTable .. '</div>' .. indexTable ..
		'</div>'
		
	)
end

function p.iconCredits(frame)
	initLangModule()
	
	local defaults = require('Module:POTY/config')
	-- note that we don't load year config, icons don't change
	local settings = {}
	local collection = {}

	-- Merge specific into the default settings (no deep-merge!)
	deepmerge(settings, defaults)

	local galleryIcons = {}
	for i, v in pairs(settings.galleryIcons) do
		if (not galleryIcons[v]) then
			galleryIcons[v] = true
			table.insert(collection, '<div style="margin:0.5em">[[File:' .. v .. '|48px]] [[:File:' .. v .. ']]</div>')
		end
	end
	
	return frame:preprocess(table.concat(collection, ''))
end

local function galleryHeader(gallerybase, headings, candidates, settings, year)
	local g = {}
	for i, v in ipairs(headings) do
		local hId = v.hId:lower()
		local f = settings.galleryIcons[hId]
		local cat = msg('cat-' .. hId)
		table.insert(g, '[[File:' .. (f or '1x1.png') .. '|25px|' .. cat .. '|link=' .. gallerybase .. '/' .. v.hId .. ']] [[' .. gallerybase .. '/' .. v.hId .. '|' .. cat .. ']] (' .. #candidates[v.hId] .. ')')
	end
	
	local tableContent = {}
	for i = 0, (math.ceil(#headings / 4) - 1), 1 do
		local b = 4*i
		table.insert(tableContent, (g[b+1] or '') .. '</td><td>' .. (g[b+2] or '') .. '</td><td>' .. (g[b+3] or '') .. '</td><td>' .. (g[b+4] or ''))
	end
	
	return '<table class="wikitable" width=100%><tr><th colspan="4" style="background:' ..
		(settings.stateColorTemplate or ('{{POTY' .. year .. '/state/color}}')) ..
		'">' .. msg('galleries-by-categories') .. '</th></tr><tr><td>' .. table.concat(tableContent, '</td></tr><tr><td>') .. '</td></tr>' ..
		'<tr><th colspan="4" style="' .. (settings.stateColorTemplate3 or ('{{POTY' .. year .. '/state/color3}}')) .. '">' .. '[[' .. (settings.helpPage or  'Special:MyLanguage/Commons:Picture of the Year/' .. year .. '/Help') .. '|<span style="' .. (settings.stateColorTemplate3 or ('{{POTY' .. year .. '/state/color3}}')) .. '">' .. lang:ucfirst(msg('help')) .. '</span>]] · [[' .. (settings.aboutIconPage or  'Special:MyLanguage/Commons:Picture of the Year/' .. year .. '/About Icons') .. '|<span style="' .. (settings.stateColorTemplate3 or ('{{POTY' .. year .. '/state/color3}}')) .. '">' .. msg('about-icons') .. '</span>]]' .. 
		'</th></tr></table>'
end


function p.galleryHeader(frame)
	initLangModule()
	
	local newLang = frame.args['lang']
	if newLang then userLang = newLang end
	local title = frame:getParent():getTitle()
	local titlesplit = mw.text.split(title, '/', true)
	local year = titlesplit[2]
	table.remove(titlesplit)
	-- TODO: This must not be hardcoded; use settings instead
	local gallerybase = 'Commons:Picture of the Year/' .. year .. '/R1/Gallery'
	local defaults = require('Module:POTY/config')
	local yconfig = require('Module:POTY/config/' .. year)
	local parser = require('Module:POTY/parser')
	local settings = {}
	
	-- Merge specific into the default settings (no deep-merge!)
	deepmerge(settings, defaults)
	deepmerge(settings, yconfig)

	if not settings.candidateListing or 0 == #settings.candidateListing then
		return msg('no-candidate-listing', 'Module:POTY/config/' .. year)
	end
	local candidates, headings = parser.parse(settings.candidateListing)

	return frame:preprocess( galleryHeader(gallerybase, headings, candidates, settings, year) )
end

function p.cand2cat(frame)
	initLangModule()
	
	local title = frame:getParent():getTitle()
	local titlesplit = mw.text.split(title, '/', true)
	local year = titlesplit[2]
	local defaults = require('Module:POTY/config')
	local yconfig = require('Module:POTY/config/' .. year)
	local parser = require('Module:POTY/parser')
	local settings = {}
	local candidateList, candidateList2 = {}, {}
	
	-- Merge specific into the default settings (no deep-merge!)
	deepmerge(settings, defaults)
	deepmerge(settings, yconfig)

	if not settings.candidateListing or 0 == #settings.candidateListing then
		return msg('no-candidate-listing', 'Module:POTY/config/' .. year)
	end
	local candidates, headings = parser.parse(settings.candidateListing)
	
	for hId, group in pairs(candidates) do
		for i, file in ipairs(group) do
			table.insert( candidateList, file.name .. '||' .. hId )
			candidateList2[file.name] = hId
		end
	end
	return '<pre>' .. table.concat( candidateList, '\n' ) .. '</pre>\n\n\n<pre style="white-space:pre-wrap">' .. mw.text.jsonEncode(candidateList2) .. '</pre>';
end

function p.gallery(frame)
	initLangModule()
	
	local title = frame:getParent():getTitle()
	local titlesplit = mw.text.split(title, '/', true)
	local year = titlesplit[2]
	local galleryname = titlesplit[#titlesplit]
	table.remove(titlesplit)
	local gallerybase = table.concat(titlesplit, '/')
	table.remove(titlesplit)
	local roundbase = table.concat(titlesplit, '/')
	local defaults = require('Module:POTY/config')
	local yconfig = require('Module:POTY/config/' .. year)
	local parser = require('Module:POTY/parser')
	local settings = {}
	local pano = frame.args.pano
	
	-- Merge specific into the default settings (no deep-merge!)
	deepmerge(settings, defaults)
	deepmerge(settings, yconfig)

	if not settings.candidateListing or 0 == #settings.candidateListing then
		return msg('no-candidate-listing', 'Module:POTY/config/' .. year)
	end
	local candidates, headings = parser.parse(settings.candidateListing)
	local sets = parser.parseSets(settings.candidateListing .. '/Sets')
	local candidates4gallery = candidates[galleryname]
	
	local galleryHeader = (settings.galleryHeaderTemplate or galleryHeader(gallerybase, headings, candidates, settings, year))
	local headerTemplate = '__NOEDITSECTION__' .. (settings.headerTemplate or ('{{POTY' .. year .. '|header}}'))
	local category = (settings.category or '[[Category:POTY ' .. year .. ']]')
	local thumbSizeBar = '<div style="text-align:{{dir|{{int:lang}}|left|right}}"><span class="thumb-size-bar" style="display:none"><span class="thumb-size-text">{{int:com-poty-thumbsize}}</span></span></div>'
	local setGalleries = {}
	local addCandidate = function(c)
		if not sets[c] then return end
		
		local setGalleryContent = {}
		for _, f in ipairs(sets[c]) do
			table.insert( setGalleryContent, f .. ((f == c) and ('|listed above') or ('|belongs to file set')) )
		end
		table.insert( setGalleries, '<gallery ' .. (pano and #pano and 'widths=800 heights=175 perrow=1>' or 'widths=300 heights=300>') .. '\n' .. table.concat(setGalleryContent, '\n') .. '\n</gallery>' )
	end
	local getSetGallery = function()
		return ((#setGalleries > 0) and ('<div class="poty-file-sets">\n<h2>' .. msg('file-sets') .. '</h2>\n' .. table.concat(setGalleries, '\n') .. '</div>') or (''))
	end
	
	if 'ALL' == galleryname then
		local candidateList = {}
		for hId, group in pairs(candidates) do
			for i, file in ipairs(group) do
				table.insert( candidateList, file )
			end
		end
		table.sort( candidateList, function(a, b) return (a.fpId or '') < (b.fpId or '') end )
		for i, file in ipairs(candidateList) do
			candidateList[i] = file.name .. '|' .. (file.fpId or '<span class="error">Missing fpId!</span>')
		end
		
		return frame:preprocess(headerTemplate .. galleryHeader ..
			'<div style="text-align:{{dir|{{int:lang}}|left|right}}">' .. msg('total', #candidateList .. ' | ' .. msg('no-voting-on-this-index-page')) .. '</div>' ..
			'<gallery mode="packed">' .. '\n' .. table.concat(candidateList, '\n') .. '</gallery>' ..
			galleryHeader  ..
			category)
	elseif galleryname:find('%d%d%d%d%-[AB]') then
		local candidateList, candidateList2 = {}, {}
		local fpIdRegExpA = year .. '%-0[1-6]'
		local isA = not not galleryname:find('%d%d%d%d%-A')
		local add = isA and 0 or 6
		for hId, group in pairs(candidates) do
			for i, file in ipairs(group) do
				table.insert( candidateList, file )
			end
		end
		table.sort( candidateList, function(a, b) return (a.fpId or '') < (b.fpId or '') end )
		for i, file in ipairs(candidateList) do
			local matchesA = (file.fpId or ''):find(fpIdRegExpA)
			if (matchesA and isA) or (not matchesA and not isA) then
				table.insert( candidateList2, file.name .. '|' .. (file.fpId or '<span class="error">Missing fpId!</span>') )
			end
		end
		
		return frame:preprocess(headerTemplate .. galleryHeader .. '\n' ..
			'<h2 style="' .. settings.galleryHeadingStyle .. '" id="poty-gallery-heading"> ' .. msg('from-to-heading', '{{int:' .. settings.months[1+add] .. '}}', '{{int:' .. settings.months[6+add] .. '}}') .. ' </h2>\n' ..
			'<div style="text-align:{{dir|{{int:lang}}|left|right}}">' .. msg('total', #candidateList2 .. ' | ' .. msg('no-voting-on-this-index-page')) .. '</div>' ..
			'<gallery mode="packed">' .. '\n' .. table.concat(candidateList2, '\n') .. '</gallery>' ..
			galleryHeader  ..
			category)
	elseif galleryname:find('M%d%d') then
		local candidateList, candidateList2 = {}, {}
		local month = galleryname:match('M(%d%d)')
		local nMonth = tonumber(month)
		local prevMonth = 'M' .. pad(tostring( zero212( nMonth - 1 ) ), 2, '0')
		local nextMonth = 'M' .. pad(tostring( zero212( (nMonth + 1) % 12 ) ), 2, '0')
		local prevNext = '\n{{previous|' .. gallerybase .. '/' .. prevMonth .. '}}\n{{next|' .. gallerybase .. '/' .. nextMonth .. '}}'
		local fpId = year  .. '-' .. month
		
		for hId, group in pairs(candidates) do
			for i, file in ipairs(group) do
				table.insert( candidateList, file )
			end
		end
		table.sort( candidateList, function(a, b) return (a.fpId or '') < (b.fpId or '') end )
		
		for i, file in ipairs(candidateList) do
			if (file.fpId or ''):find(fpId, 1, true) then
				table.insert(candidateList2, file.name .. '|' .. (file.fpId or '<span class="error">Missing fpId!</span>'))
				addCandidate(file.name)
			end
		end
		return frame:preprocess(headerTemplate .. galleryHeader .. prevNext ..
			'\n<h2 style="' .. settings.galleryHeadingStyle .. '" id="poty-gallery-heading"> {{int:' .. settings.months[nMonth] .. '}} </h2>\n' ..
			thumbSizeBar ..
			'<div style="text-align:{{dir|{{int:lang}}|left|right}}">' .. msg('total', #candidateList2) .. '</div>' ..
			'<div id="potyEasyVoteEnhanced" style="text-align:center"><gallery widths=300 heights=300>' .. '\n' .. table.concat(candidateList2, '\n') .. '</gallery></div>' ..
			getSetGallery() .. prevNext.. galleryHeader  ..
			category)
	else
		-- TODO: Randomization?
		if not candidates4gallery then
			return msg('no-gallery-definition', settings.candidateListing)
		end
		
		
		local strlist = {}
		for i, v in ipairs(candidates4gallery) do
			table.insert(strlist, v.name .. '|{{' .. (settings.voteButton or ('POTY' .. year .. '/votebutton')) .. '|f=' .. v.name .. '|base=' .. roundbase .. '}}')
			addCandidate(v.name)
		end
		table.insert(strlist, '</gallery></div>')
		
		local desc, gPrev, gNext
		for i, v in ipairs(headings) do
			if v.hId == galleryname then
				desc = v
				gPrev = (headings[i-1] or headings[#headings]).hId
				gNext = (headings[i+1] or headings[1]).hId
				break
			end
		end
		
		-- Make the header:
		local prevNext = '\n{{previous|' .. gallerybase .. '/' .. gPrev .. '}}\n{{next|' .. gallerybase .. '/' .. gNext .. '}}'
		local header = headerTemplate ..
			prevNext ..
			(pano and #pano and '<div id="potyWide"><!-- Make POTY js aware of that. --></div>' or '') ..
			'\n<h2 style="' .. settings.galleryHeadingStyle .. '" id="poty-gallery-heading"> ' .. msg('cat-' .. desc.hId:lower()) .. '</h2>\n' ..
			thumbSizeBar ..
			'<div style="text-align:{{dir|{{int:lang}}|left|right}}">' .. msg('total', #candidates4gallery) .. '</div><div id="potyEasyVoteEnhanced" style="text-align:center">'
		local footer = prevNext .. 
			galleryHeader ..
			category
		
		return frame:preprocess(header ..
			'<gallery ' .. (pano and #pano and 'widths=800 heights=175 perrow=1>' or 'widths=300 heights=300>') .. '\n' .. table.concat(strlist, '\n') ..
			getSetGallery() ..
			footer)
	end
end

function p.votePage(frame)
	initLangModule()
	
	local c = mw.text.decode(frame.args['for'])
	
	local title = frame:preprocess('{{FULLPAGENAME}}')
	local titlesplit = mw.text.split(title, '/', true)
	local year = titlesplit[2]
	local defaults = require('Module:POTY/config')
	local yconfig = require('Module:POTY/config/' .. year)
	local parser = require('Module:POTY/parser')
	local settings = {}

	-- Merge specific into the default settings (no deep-merge!)
	deepmerge(settings, defaults)
	deepmerge(settings, yconfig)

	local sets = parser.parseSets(settings.candidateListing .. '/Sets')
	if not sets[c] then return '[[File:' .. c .. '|thumb|500x500px|' .. c .. ']]' end
	
	local setGalleryContent = {}
	for _, f in ipairs(sets[c]) do
		table.insert( setGalleryContent, '|File:' .. f .. '|' .. f .. ((f == c) and (' - listed') or ('- belongs to file set')) )
	end
	
	return frame:preprocess('\n<h2>' .. msg('file-sets') .. '</h2>\n{{Imagestack\n|width=500|loop=yes\n' .. table.concat(setGalleryContent, '\n') .. '\n}}')
end

function p.showOneWinner(frame)
	local title = frame:preprocess('{{FULLPAGENAME}}')
	local titlesplit = mw.text.split(title, '/', true)
	local year = frame.args['year'] or titlesplit[2]
	local defaults = require('Module:POTY/config')
	local yconfig = require('Module:POTY/config/' .. year)
	local parser = require('Module:POTY/parser')
	local settings = {}
	local to = tonumber(frame.args['to'] or 3)
	local pattern = frame.args['pattern'] or '[[File:%filename%|350x400px|center|frameless|%caption%|alt=Picture of the Year Winner]]<div style="margin:0,1em,1em,1em; text-align:center">%caption%</div>'
	pattern = mw.text.unstrip(pattern)

	-- Merge specific into the default settings (no deep-merge!)
	deepmerge(settings, defaults)
	deepmerge(settings, yconfig)
	
	local winners = parser.parseWinners(settings.winnerListing)
	local winnerCount = #winners
	if winnerCount == 0 then
		return frame:preprocess('winner page [[' .. settings.winnerListing .. ']] could not be parsed')
	end
	to = math.min(winnerCount, to)
	
	-- now choose a random winner within the range
	-- lua has a very convenient function for that
	-- but set the seed first
	math.randomseed( os.time() )
	
	local winnerIndex = math.random( 1, to )
	local winner2show = winners[winnerIndex]
	
	local caption = winner2show.caption:match('{{POTY/caption%|%d%d%d%d%-%d%d%-%d%d}}')
	local filename = winner2show.name
	
	return frame:preprocess(pattern:gsub('%%filename%%', filename):gsub('%%caption%%', caption))
end

return p