Module:FileInfo

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 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)

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("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 else gameDisplay = "" end end local type = args.type and Data.types[args.type] type = type and type.category

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", type) 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/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 if typeCat and not typeCat.nogame and gameName and game ~= "Series" then categories = categories..string.format("", gameName, typeCat) elseif typeCat then categories = categories..string.format("", typeCat) elseif gameCat then categories = categories..string.format("", gameName) end if not game then -- not necessarily invalid but worth tracking categories = categories.."" 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  is not a valid 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.."" 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("", 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 = "nogame", type = "boolean", desc = "If, indicates that images of this type di not apply to any particular entry in the Zelda franchise and therefore its category should not be subdivided by game.", },						},					},				},				{					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.nogame 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 = " "		local typeCategory = "Category:"..type.category..""		return {typeCode, typeCategory}	end) table.insert(dataRows, 1, {		{			header = true,			colspan = 2,			content = "Game Types"		}	}) table.insert(dataRows, #gameTypes + 2, {		{			header = true,			colspan = 2,			content = "Concept Types",		}	}) table.insert(dataRows, #gameTypes + #conceptTypes + 3, {		{			header = true,			colspan = 2,			content = "Non-Game Types",		}	}) return utilsLayout.table({		headers = {"Type", "Category"},		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 = " " 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

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", type = "string", desc = "The type of file, which determines how it is 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 = "A valid code for a game, book, comic, manga, or TV show (or ).", enum = Franchise.enum({ 					includeSeries = true,					includeGroups = true,				}), trim = true, nilIfEmpty = true, },			licensing = { required = "Category:Unlicensed Files", type = "string", desc = "The copyright licensing for the file. For the vast majority of files,  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 trademarks (usually denoted by an ® or ™ symbol).", trim = true, nilIfEmpty = true, }		}	} }

return p