Module:FileInfo
Jump to navigation
Jump to search
This is the main module for the following templates:
local p = {}
local h = {}
local Data = mw.loadData("Module:FileInfo/Data")
-- It's important to minimize the number of imports because this module is linked on every file page
-- Any change to these modules will trigger a large MediWiki job queue
local cargo = mw.ext.cargo
local Franchise = require("Module:Franchise")
local utilsArg = require("Module:UtilsArg")
local MAX_IMAGE_AREA = require("Module:Constants/number/maxImageArea")
function h.warn(msg)
local utilsError = require("Module:UtilsError")
utilsError.warn(msg)
end
function p.StoreWidth(frame)
return mw.title.getCurrentTitle().file.width
end
function p.StoreHeight(frame)
return mw.title.getCurrentTitle().file.height
end
function p.Main(frame)
local args = frame:getParent().args
local args = h.preformat(args)
local args, err = utilsArg.parse(args, p.Templates.FileInfo)
local categories = err and err.categoryText or ""
local result = h.printFileInfoTable(frame, args)
categories = categories..h.categories(args.type, args.game, args.subject)
-- Avoid categories from being added to [[MediaWiki:Upload-default-description]] and [[MediaWiki:Msu-comment]]
if mw.title.getCurrentTitle().nsText == "MediaWiki" then
categories = ""
end
return categories, result -- categories first in case there's notices attached to them - see h.maintenanceCategories()
end
function h.printFileInfoTable(frame, args)
local gameDisplay
if args.game then
local gameLogo = Franchise.logo(args.game)
local gameImage = gameLogo and gameLogo ~= "" and string.format("[[%s|130x130px]]", gameLogo)
local gameLink = Franchise.link(args.game)
local gameText = gameLink and string.format("This is a file pertaining to %s.", gameLink)
if gameImage and gameText then
gameDisplay = gameImage .. " " .. gameText
elseif gameText then
gameDisplay = gameText
end
end
local type = args.type and Data.types[args.type]
local typeLink = type and type.category and string.format("[[:Category:%s|%s]]", type.category, args.type)
local license
if args.licensing and h.licenseExists(args.licensing) then
license = frame:expandTemplate({
title = "FileInfo/License/" .. args.licensing,
args = {
trademark = args.trademark
}
})
else
license = frame:expandTemplate({ title = "FileInfo/License/Unsure" })
end
local html = mw.html.create("table"):addClass("wikitable fileinfo")
h.row(html, "Summary", args.summary)
h.row(html, "Type", typeLink)
h.row(html, "Source", args.source or frame:expandTemplate({ title = "No Source" }))
h.row(html, "Game", gameDisplay)
h.row(html, "Licensing", license, {
rowspan = args.trademark and "2" or "1"
})
h.row(html, "Trademark", args.trademark and frame:expandTemplate({ title = "FileInfo/License/Trademark" }))
return tostring(html)
end
function h.row(html, field, value, attributes)
if value then
return html
:tag("tr")
:tag("th")
:wikitext(field)
:done()
:tag("td")
:wikitext(value)
:done()
:done()
end
end
function h.categories(type, game, subjects)
local categories = ""
categories = categories..h.gameTypeCategories(game, type)
categories = categories..h.subjectCategories(subjects)
categories = categories..h.maintenanceCategories(type)
return categories
end
function h.gameTypeCategories(game, type)
local categories = ""
local gameName = game and Franchise.shortName(game)
local typeCat = type and Data.types[type] and Data.types[type].category
local gameRequired = type and Data.types[type] and Data.types[type].gameRequired
if typeCat and gameName and game ~= "Series" then
categories = categories..string.format("[[Category:%s %s]]", gameName, typeCat)
elseif typeCat then
categories = categories..string.format("[[Category:%s]]", typeCat)
elseif gameCat then
categories = categories..string.format("[[Category:%s Files]]", gameName)
end
if game == "N/A" then
categories = categories.."[[Category:Files with inapplicable game]]"
end
if type and gameRequired ~= false and not game then
local errMsg = string.format("<code>game</code> parameter is required for file type <code>%s</code>.", type)
.. " For a list of accepted game values, see [[Data:Franchise]]."
.. " If none of these values apply, set the <code>game</code> parameter to <code>N/A</code>."
h.warn(errMsg)
categories = categories.."[[Category:Files lacking game info]]"
end
return categories
end
function h.subjectCategories(subjects)
if not subjects then
return ""
end
local Term = require("Module:Term")
local categories = ""
for i, subject in ipairs(subjects) do
local term, errCategories = Term.fetchTerm(subject, "Series")
if not term then
local utilsError = require("Module:UtilsError")
local utilsMarkup = require("Module:UtilsMarkup")
utilsError.warn(string.format("subject <code>%s</code> is not a valid [[Template:Term|term]]", subject))
categories = categories..utilsMarkup.categories(errCategories)
else
term = string.gsub(term, "#", "") -- strip # from term because categories can't have them in their name
-- only add subject-based categories if they already exist, to avoid spamming Special:WantedCategories
if h.pageExists("Category:Images of "..term) then
categories = categories.."[[Category:Images of "..term.."]]"
end
end
end
return categories
end
function h.maintenanceCategories(type)
local categories = ""
local frame = mw.getCurrentFrame()
local title = mw.title.getCurrentTitle()
local mimeType = title.file and title.file.mimeType
if type == "Sprite" and mimeType == "image/gif" then
categories = categories..frame:expandTemplate({ title = "FileInfo/Notices/GIF Sprite" })
end
local exceedsMaxImageArea = title.file and ((title.file.width or 0) * (title.file.height or 0)) > MAX_IMAGE_AREA
if mimeType ~= "image/jpeg" and exceedsMaxImageArea then
categories = categories..frame:expandTemplate({ title = "FileInfo/Notices/Oversized" })
end
-- See category page for why we do this ourselves instead of using Special:UnusedFiles
if title.nsText == "File" and h.isUnused(title.text) then
categories = categories..frame:expandTemplate({ title = "FileInfo/Notices/Unused" })
end
return categories
end
function h.isUnused(filename)
local separator = "$separator$"
local dplQuery = string.format("{{#dpl:|imageused=%s|count=2}}", filename, separator)
local dplResult = mw.getCurrentFrame():preprocess(dplQuery)
return dplResult == ""
end
-- For supporting types written in lower case or Title Case
-- Previously only the former was supported but editors had a tendency to use the latter
-- Which is understandable because the type category is in title case, Template:Media is in title case,
-- and all other fields in FileInfo are in title case or sentence case
-- We may eventually decide to support only Title Case but that would mean
-- running a text-replace on the thousands of file pages using lowercase
function h.preformat(args)
local _args = {}
for k, v in pairs(args) do
if k == "type" then
_args[k] = string.gsub(" "..v, "%W%l", string.upper):sub(2)
else
_args[k] = v
end
end
return _args
end
function h.licenseExists(license)
for i, definedLicense in ipairs(Data.licenses) do
if license == definedLicense then
return true
end
end
return false
end
-- Copied from [[Module:UtilsPage]] to reduce the number of imports
function h.pageExists(fullPageName, noRedirect)
local anchorStart = string.find(fullPageName, "#")
if anchorStart then
fullPageName = string.sub(fullPageName, 1, anchorStart - 1)
end
fullPageName = string.gsub(fullPageName, "'", "\\'") -- escape apostrophes
local queryResults = cargo.query("_pageData", "_pageName, _isRedirect", {
where = string.format("_pageName = '%s'", fullPageName)
})
return #queryResults > 0 and (not noRedirect or queryResults[1]._isRedirect == "0")
end
function p.Schemas()
return {
Data = {
type = "record",
required = true,
properties = {
{
name = "types",
required = true,
type = "map",
desc = "Defines file types, associating a type name to a wiki category.",
keys = { type = "string"},
keyPlaceholder = "type name",
values = {
type = "record",
properties = {
{
name = "category",
required = true,
type = "string",
desc = "The name of a subcategory of [[:Category:Files by Type]].",
},
{
name = "gameRequired",
type = "boolean",
default = true,
desc = "Set this to <code>false</code> to indicate that images of this type may not apply to a particular [[Data:Franchise|entry]] in the ''Zelda'' franchise. This renders the FileInfo <code>game</code> parameter optional for the given type. Otherwise, files missing this parameter are added to [[:Category:Files lacking game info]].",
},
},
},
},
{
name = "licenses",
required = true,
type = "array",
items = { type = "string" },
desc = "A list of licenses supported by [[Template:FileInfo]]. Each license must have a subpage under [[Template:FileInfo/License]].",
},
},
}
}
end
function p.Data(frame)
local result = ""
result = result .. "\n==Types==\n"
result = result .. h.typeTable()
result = result .. "\n==Licenses==\n"
result = result .. h.licenseTable()
return result
end
function h.typeTable()
local utilsLayout = require("Module:UtilsLayout")
local utilsString = require("Module:UtilsString")
local utilsTable = require("Module:UtilsTable")
local gameTypes = {}
local conceptTypes = {}
local otherTypes = {}
for typeName, type in pairs(Data.types) do
type = utilsTable.merge({}, type, {
name = typeName
})
if utilsString.startsWith(type.name, "Concept") then
table.insert(conceptTypes, type)
elseif type.gameRequired == false then
table.insert(otherTypes, type)
else
table.insert(gameTypes, type)
end
end
gameTypes = utilsTable.sortBy(gameTypes, "name")
conceptTypes = utilsTable.sortBy(conceptTypes, "name")
otherTypes = utilsTable.sortBy(otherTypes, "name")
local types = utilsTable.concat(gameTypes, conceptTypes, otherTypes)
local dataRows = utilsTable.map(types, function(type)
local typeCode = "<code>"..type.name.."</code>"
local typeCategory = "[[:Category:"..type.category.."]]"
local gameRequiredOrOptional = type.gameRequired == true and "Required" or "Optional"
return {typeCode, typeCategory, gameRequiredOrOptional}
end)
table.insert(dataRows, 1, {
{
header = true,
colspan = 3,
content = "Game Types"
}
})
table.insert(dataRows, #gameTypes + 2, {
{
header = true,
colspan = 3,
content = "Concept Types",
}
})
table.insert(dataRows, #gameTypes + #conceptTypes + 3, {
{
header = true,
colspan = 3,
content = "Other Types",
}
})
return utilsLayout.table({
headers = {"Type", "Category", "<code>game</code> Required?"},
rows = dataRows,
})
end
function h.licenseTable()
local utilsLayout = require("Module:UtilsLayout")
local utilsTable = require("Module:UtilsTable")
return utilsLayout.table({
sortable = {1},
headers = {"License", "Template", "Output"},
rows = utilsTable.map(Data.licenses, function(license)
local licenseCode = "<code>"..license.."</code>"
local template = "FileInfo/License/"..license
local templateLink = "[[Template:"..template.."]]"
local templateOutput = mw.getCurrentFrame():expandTemplate({title = template})
return {licenseCode, templateLink, templateOutput}
end)
})
end
function p.enumTypes()
local enum = {}
for k in pairs(Data.types) do
table.insert(enum, k)
end
enum.reference = "[[Module:FileInfo/Data]]"
return enum
end
function p.enumLicenses()
local enum = {}
for i, license in ipairs(Data.licenses) do
enum[i] = license
end
enum.reference = "[[Module:FileInfo/Data]]"
return enum
end
function p.enumGames()
local enum = {"N/A"}
local franchiseEnum = Franchise.enum({
includeSeries = true,
includeNonfiction = true,
includeGroups = true,
})
for i, entry in ipairs(franchiseEnum) do
table.insert(enum, entry)
end
enum.reference = "[[Data:Franchise]]"
return enum
end
p.Templates = {
FileInfo = {
purpose = "Displays, categorizes, and stores file information. See [[Guidelines:Files]] for further guidance.",
format = "block",
paramOrder = {"summary", "type", "source", "game", "licensing", "subject", "trademark"},
boilerplate = {
tabs = {
{
label = "Zelda-Related Files",
params = {"summary", "type", "source", "game", "licensing", "subject"},
},
{
label = "Other Files",
params = {"summary", "type", "source", "licensing"},
},
{
label = "All Parameters",
params = {"summary", "type", "source", "game", "licensing", "subject", "trademark"},
},
},
},
params = {
summary = {
--required = true,
type = "content",
desc = "A short description of the file.",
trim = true,
nilIfEmpty = true,
},
type = {
required = "Category:Files lacking type info",
type = "string",
desc = "The type of file, which determines how it is [[:Category:Files by Type|categorized]].",
enum = p.enumTypes(),
trim = true,
nilIfEmpty = true,
},
source = {
required = "Category:Files Lacking Sources",
type = "string",
desc = "The original source of the file. It may be in the form of a URL or author recognition. [[Template:Source]] exists for this purpose.",
trim = true,
nilIfEmpty = true,
},
subject = {
type = "string",
desc = "Wiki article names of all the subjects depicted in the file. A comma-separated list.",
split = true,
trim = true,
nilIfEmpty = true,
},
game = {
--required = true,
type = "string",
desc = "<p>A valid [[Data:Franchise]] code for a game, book, comic, manga, or TV show (or <code>Series</code>).</p>"
.."<p>Required for game-based types such as <code>Artwork</code>, <code>Screenshot</code>, <code>Sprite</code>, etc. See [[Module:FileInfo/Data]] for full list.</p>"
.."<p><code>game</code> can be set to <code>N/A</code> when a game is required for the type but no Data:Franchise code applies, namely for images of [[The Legend of Zelda in Popular Culture|unlicensed media]].",
enum = p.enumGames(),
trim = true,
nilIfEmpty = true,
},
licensing = {
required = "Category:Unlicensed Files",
type = "string",
desc = "The copyright licensing for the file. For the vast majority of files, <code>Copyright</code> is the correct value here.",
enum = p.enumLicenses(),
trim = true,
nilIfEmpty = true,
},
trademark = {
type = "boolean",
desc = "Enter any text to add a trademark notice to the licensing. Use on all [[:Category:Trademarks|trademarks]] (usually denoted by an ® or ™ symbol).",
trim = true,
nilIfEmpty = true,
}
}
}
}
return p