Module:Extension

local lang = mw.language.getContentLanguage local translation = mw.getCurrentFrame:callParserFunction{name='#translation', args="1"} local addr = { GNU = '//www.gnu.org/licenses/', OSI = '//opensource.org/licenses/', CC = '//creativecommons.org/licenses/', Mozilla = '//www.mozilla.org/' } local cats = { GPL   = 'GPL licensed extensions', FDL   = 'FDL licensed extensions', LGPL  = 'LGPL licensed extensions', AGPL  = 'AGPL licensed extensions', MIT   = 'MIT licensed extensions', ISC   = 'ISC licensed extensions', BSD   = 'BSD licensed extensions', MPL   = 'MPL licensed extensions', WTFPL = 'WTFPL licensed extensions', Apache = 'Apache licensed extensions', PD    = 'Public domain licensed extensions', CC    = 'Creative Commons licensed extensions', ECL   = 'Educational Community licensed extensions', Unlicense = 'The Unlicense licensed extensions', BLANK = 'Extensions with no license specified' } local licenses = { ['AGPL-3.0']    = { addr.GNU .. 'agpl-3.0.html', 'GNU Affero General Public License 3.0', 'AGPL' }, ['AGPL-3.0-only']    = { addr.GNU .. 'agpl-3.0.html', 'GNU Affero General Public License 3.0', 'AGPL' }, ['AGPL-3.0-or-later'] = { addr.GNU .. 'agpl-3.0.html', 'GNU Affero General Public License 3.0 or later', 'AGPL' }, ['Apache-2.0']  = { '//www.apache.org/licenses/LICENSE-2.0', 'Apache License 2.0', 'Apache' }, ['BSD-2-Clause'] = { addr.OSI .. 'BSD-2-Clause', 'BSD 2-clause "Simplified" License', 'BSD' }, ['BSD-3-Clause'] = { addr.OSI .. 'BSD-3-Clause', 'BSD 3-clause "Modified" License', 'BSD' }, ['BSD-4-Clause'] = { addr.GNU .. 'license-list.html#OriginalBSD', 'BSD 4-clause "Original" License', 'BSD' }, ['CC-BY-3.0']   = { addr.CC .. 'by/3.0/', 'Creative Commons Attribution 3.0', 'CC' }, ['CC-BY-3.0-US'] = { addr.CC .. 'by/3.0/us/', 'Creative Commons Attribution 3.0 United States', 'CC' }, ['CC-BY-NC-3.0'] = { addr.CC .. 'by-nc/3.0/', 'Creative Commons Attribution NonCommercial 3.0', 'CC' }, ['CC-BY-NC-SA-2.5'] = { addr.CC .. 'by-nc-sa/2.5/', 'Creative Commons Attribution NonCommercial Share Alike 2.5', 'CC' }, ['CC-BY-NC-SA-3.0'] = { addr.CC .. 'by-nc-sa/3.0/', 'Creative Commons Attribution NonCommercial Share Alike 3.0', 'CC' }, ['CC-BY-NC-SA-4.0'] = { addr.CC .. 'by-nc-sa/4.0/', 'Creative Commons Attribution NonCommercial Share Alike 4.0', 'CC' }, ['CC-BY-SA-2.0'] = { addr.CC .. 'by-sa/2.0/', 'Creative Commons Attribution Share Alike 2.0', 'CC' }, ['CC-BY-SA-2.0-UK'] = { addr.CC .. 'by-sa/2.0/uk/', 'Creative Commons Attribution Share Alike 2.0 England and Wales', 'CC' }, ['CC-BY-SA-2.5'] = { addr.CC .. 'by-sa/2.5/', 'Creative Commons Attribution Share Alike 2.5', 'CC' }, ['CC-BY-SA-3.0'] = { addr.CC .. 'by-sa/3.0/', 'Creative Commons Attribution Share Alike 3.0', 'CC' }, ['CC-BY-SA-4.0'] = { addr.CC .. 'by-sa/4.0/', 'Creative Commons Attribution Share Alike 4.0', 'CC' }, ['CC0-1.0']     = { '//creativecommons.org/publicdomain/zero/1.0/', 'Creative Commons Zero v1.0 Universal', 'PD' }, ['ECL-2.0']     = { '', 'Educational Community License 2.0', 'ECL' }, ['FDL']         = { addr.GNU .. 'fdl.html', 'GNU Free Documentation License', 'FDL' }, ['GPL-2.0']     = { addr.GNU .. 'old-licenses/gpl-2.0-standalone.html', 'GNU General Public License 2.0', 'GPL' }, ['GPL-2.0-only'] = { addr.GNU .. 'old-licenses/gpl-2.0-standalone.html', 'GNU General Public License 2.0 only', 'GPL' }, ['GPL-2.0-or-later'] = { addr.GNU .. 'old-licenses/gpl-2.0-standalone.html', 'GNU General Public License 2.0 or later', 'GPL' }, ['GPL-3.0']     = { addr.GNU .. 'gpl-3.0-standalone.html', 'GNU General Public License 3.0', 'GPL' }, ['GPL-3.0-only'] = { addr.GNU .. 'gpl-3.0-standalone.html', 'GNU General Public License 3.0 only', 'GPL' }, ['GPL-3.0-or-later'] = { addr.GNU .. 'gpl-3.0-standalone.html', 'GNU General Public License 3.0 or later', 'GPL' }, ['ISC']         = { addr.OSI .. 'ISC', 'ISC License', 'ISC' }, ['LGPL-2.1']    = { addr.GNU .. 'old-licenses/lgpl-2.1-standalone.html', 'GNU Lesser General Public License 2.1', 'LGPL' }, ['LGPL-2.1-only'] = { addr.GNU .. 'old-licenses/lgpl-2.1-standalone.html', 'GNU Lesser General Public License 2.1 only', 'LGPL' }, ['LGPL-2.1-or-later'] = { addr.GNU .. 'old-licenses/lgpl-2.1-standalone.html', 'GNU Lesser General Public License 2.1 or later', 'LGPL' }, ['LGPL-3.0']    = { addr.GNU .. 'lgpl-3.0-standalone.html', 'GNU Lesser General Public License 3.0', 'LGPL' }, ['LGPL-3.0-only'] = { addr.GNU .. 'lgpl-3.0-standalone.html', 'GNU Lesser General Public License 3.0 only', 'LGPL' }, ['LGPL-3.0-or-later'] = { addr.GNU .. 'lgpl-3.0-standalone.html', 'GNU Lesser General Public License 3.0 or later', 'LGPL' }, ['MIT']         = { addr.OSI .. 'mit-license.php', 'MIT License', 'MIT' }, ['MPL-1.0']     = { addr.Mozilla .. 'MPL/1.0/', 'Mozilla Public License 1.0', 'MPL' }, ['MPL-2.0']     = { addr.Mozilla .. 'MPL/2.0/', 'Mozilla Public License 2.0', 'MPL' }, ['PD']          = { '', 'Public domain', 'PD' }, ['Unlicense']   = { 'https://unlicense.org/', 'The Unlicense', 'Unlicense' }, ['WTFPL']       = { 'http://www.wtfpl.net', 'WTFPL 2.0', 'WTFPL' }, ['Zlib']        = { addr.OSI .. 'Zlib', 'zlib License' }, ['unspecified'] = { '', 'No license specified', 'BLANK'} } local types = { ajax               = { 'Ajax',                                        'Ajax extensions' }, api                = { 'API',                                 'API extensions' }, ['beta feature']   = { 'Beta Feature',                         'Beta Feature extensions' }, contenthandler     = { 'ContentHandler',               'ContentHandler extensions' }, database           = { 'Database',                    'Database extensions' }, ['data extraction'] = { 'Data extraction',                                       'Data extraction extensions' }, example            = { 'Example',                                                'Extension examples' }, ['extended syntax'] = { 'Extended syntax',      'Extended syntax extensions' }, filerepo           = { 'File repository',                                        'File repository extensions' }, hook               = { 'Hook',                                  'Hook extensions' }, interface          = { 'User interface',                                         'User interface extensions' }, link               = { 'Link markup',           'Link markup extensions' }, media              = { 'Media',                                                  'Media handling extensions' }, mywiki             = { 'MyWiki',                      'Personalization extensions' }, notify             = { 'Notify',                                                 'Notification extensions' }, ['page action']    = { 'Page action', 'Page action extensions' }, parser             = { 'Parser extension',      'Parser extensions' }, ['parser function'] = { 'Parser function',           'Parser function extensions' }, php                = { 'PHP',                                                    'PHP extensions' }, search             = { 'Search',                                                 'Search extensions' }, skin               = { 'Skin',                                  'Skin extensions' }, ['special page']   = { 'Special page',                  'Special page extensions' }, locale             = { 'Locale',                         'Internationalization extensions' }, tag                = { 'Tag',                          'Tag extensions' }, ['user access']    = { 'User access',                        'User access extensions' }, ['user identity']  = { 'User identity',                      'User identity extensions' }, ['user rights']    = { 'User rights',                        'User rights extensions' }, ['user activity']  = { 'User activity',                      'User activity extensions' }, variable           = { 'Variable',                          'Variable extensions' }, }

local typeAliases = { db = 'database', pfunc = 'parser function', special = 'special page', }

local function setI18n( from, to, index ) for n, v in pairs( from ) do		if to[n] then to[n][index] = v		end end end

local function cat( title ) return '' end

local function tcat( title ) return cat( title .. translation ) end

local function getType( str, str2 ) local str = mw.ustring.lower( str ) if typeAliases[str] then str = typeAliases[str] end local cnf = types[str] local res if cnf then res = cnf[1] .. ''	else if str == '_missing_' then res = tcat( 'Extensions with invalid or missing type' ) elseif str == '_demomode_' then if str2 then res = lang:ucfirst( str2 ) else res = "unknown" end else res = ( str or '\'\'unknown\'\'' ) .. ' (\'\'\'\'\'invalid type\'\'\'\'\')' .. tcat( 'Extensions with invalid or missing type' ) end end return res end

local function getExtData local pg	local pframe = mw.getCurrentFrame:getParent if pframe and pframe.args.repo then pg = pframe.args.repo else pg = mw.title.getCurrentTitle.rootPageTitle:partialUrl -- need to get rid of translation subpage. end return mw.loadData( 'Module:ExtensionJson' )[pg] end

local function getPopularityData local pg	local pframe = mw.getCurrentFrame:getParent if pframe and pframe.args.repo then pg = pframe.args.repo else pg = mw.title.getCurrentTitle.rootPageTitle:partialUrl -- need to get rid of translation subpage. end type = 'extensions' if mw.title.getCurrentTitle.rootPageTitle:inNamespace( 'skin' ) then type ="skins" end return mw.loadData( 'Template:Extension/popularity.json' )[type][pg] end

local function getLicenseString (str) str = mw.text.trim(str) if str == "" or str == nil then local data = getExtData if data and data["license-name"] then str = data["license-name"] else str = "unspecified" end end return str end local function getLicenseCategory( str ) str = getLicenseString(str) if mw.ustring.sub( str, -1 ) == '+' then str = mw.ustring.sub( str, 1, -2 ) end local cnf = licenses[str] if cnf then if #cnf > 2 then return tcat( cats[cnf[3]] ) end else return tcat( 'Extensions with unknown license' ) end end

local function getFormattedLicense( str, orlatertext ) local orlater = '' local license = getLicenseString(str) if mw.ustring.sub( license, -1 ) == '+' then license = mw.ustring.sub( license, 1, -2 ) orlater = orlatertext end local cnf = licenses[license] if cnf then return (cnf[1] ~= '' and ('[' .. cnf[1] .. ' ' .. cnf[2] .. ']') or cnf[2]) .. orlater else return license end end

local p = {}

function p.getTypes( frame ) setI18n( frame.args, types, 1 ) local args = frame:getParent.args local types = {} local params = { args.type1 or args['type'] or 'missing', args.type2, args.type3, args.type4, args.type5, args.type6, }	for _, param in ipairs( params ) do		if param == nil or mw.text.trim( param ) == '' then break end local param = mw.text.trim( param ) if args.templatemode == 'nocats' then table.insert( types, getType( '_demomode_', param ) ) else table.insert( types, getType( param ) ) end end return table.concat( types, ', ' ) end

function p.getType( frame ) setI18n( frame.args, types, 1 ) return getType( frame.args[1] ) end

function p.getLicenseCategory( frame ) return getLicenseCategory( frame.args[1] ) end

function p.getFormattedLicense( frame ) setI18n( frame.args, licenses, 2 ) return getFormattedLicense( frame.args[1], frame.args['+'] or ' or later' ) end

-- Return if the extension does schema updates -- Only answer yes. For now be silent on no or unknown, as its unclear -- if this info should be in infobox if the answer is not yes. function p.getNeedsUpdates( frame ) local data = getExtData if data ~= nil and data.Hooks ~= nil and data.Hooks.LoadExtensionSchemaUpdates ~= nil then return 'yes' end return '' end

function p.getVersion( frame ) if frame.args[1] ~= nil and mw.text.trim(frame.args[1]) ~= "" then return frame.args[1] end local data = getExtData if data ~= nil and data.version ~= nil then return data.version end return '' end

-- -- -- Get the requires.MediaWiki value from extension.json -- @link https://www.mediawiki.org/wiki/Manual:Extension.json/Schema#requires -- -- function p.getMediaWikiRequirement( frame ) -- If the first arg is given, it'll be the manual override value. if frame.args[1] ~= nil and mw.text.trim( frame.args[1] ) ~= "" then return frame.args[1] .. cat( 'Extensions with manual MediaWiki version' ) end -- Otherwise, look it up from extension.json. local data = getExtData if data and data.requires and data.requires.MediaWiki then return data.requires.MediaWiki end local pargs = frame:getParent.args if pargs.templatemode == "nocats" then return '' end -- If neither are given, just categorize. return cat( 'Extensions without MediaWiki version' ) end

function p.getPHPRequirement( frame ) -- If the first arg is given, it'll be the manual override value. if frame.args[1] ~= nil and mw.text.trim( frame.args[1] ) ~= "" then return frame.args[1] end -- Otherwise, look it up from extension.json. local data = getExtData -- unclear if we should somehow return other platform requirements if data and data.requires and data.requires.platform and data.requires.platform.php then return data.requires.platform.php end -- Return nothing if unknown return end

-- -- -- Get the Composer name for the extension, with relevant categories. -- -- function p.getComposerName( frame ) local name = nil local hasManualName = false -- If the first arg is given, it'll be the manual override value. if frame.args[1] ~= nil and mw.text.trim( frame.args[1] ) ~= "" then name = frame.args[1] hasManualName = true end -- Otherwise, look it up from extension.json. local data = getExtData local hasComposerName = false if data and data.composer then name = data.composer hasComposerName = true end -- Tracking category for possibly mis-configured packages. if hasManualName and not hasComposerName then cat( 'Extensions without name in composer.json' ) end if name ~= nil then return '[https://packagist.org/packages/' .. name .. ' ' .. name .. ']'			.. tcat( 'Extensions supporting Composer' ) end end

function p.getHooks(frame) local hookOutput = frame.args.header local hooks = {} local index = 1 local pframe = frame:getParent local foundLocalHooks = false while true do local argument = pframe.args["hook" .. index] if argument and mw.text.trim(argument) ~= "" then hooks[#hooks + 1] = mw.text.trim(argument) foundLocalHooks = true else break end index = index + 1 end if not foundLocalHooks then local data = getExtData if data == nil or data.Hooks == nil then return "" end for hook, _ in pairs(data.Hooks) do			hooks[#hooks + 1] = hook end table.sort(hooks) end local first = true for _, hook in ipairs(hooks) do		if first then first = false else hookOutput = hookOutput .. frame.args.delim end hookOutput = hookOutput .. frame:expandTemplate{title="Extension/HookInUse",args={hook,templatemode=pframe.args.templatemode}} end return hookOutput .. frame.args.footer end function p.getParameters(frame) local data = getExtData if data == nil then return "" end local config = data.config if config == nil then return "" end local prefix = "wg" local skip_prefix = false if data.manifest_version and data.manifest_version >= 2 then if data.config_prefix then prefix = data.config_prefix end else if config._prefix then prefix = config._prefix skip_prefix = true end end local out = "" for key, v in pairs(config) do       local wrapper = ' ' if type( v ) == 'table' and v.description ~= nil then local desc = v.description if type(desc) == 'table' then -- Shallow clone in order to make the table library happy with mw.loadData desc = require("Module:TableTools").shallowClone(desc) desc = table.concat(desc, " ") end wrapper = '' end if key ~= '_prefix' or not skip_prefix then out = out .. "* " .. wrapper .. "$" .. prefix .. key .. " \n" end end return out end function p.getRights(frame) local data = getExtData if data == nil then return "" end local rights = data.AvailableRights if rights == nil then return "" end local out = "" -- Module:ExtensionJson starts at zero, but Lua tables start at 1 local iter, table_ = ipairs(rights) for _, right in iter, table_, -1 do out = out .. "* " .. right .. "\n" end return out end function p.unmaintained(frame) local content = mw.title.getCurrentTitle:getContent if not content:find("{{[uU]nmaintained extension") and not content:find("{{TNT|[uU]nmaintained extension") and not content:find("{{User:Jeroen[ _]De[ _]Dauw/unmaintained") then local args = {} local pargs = frame:getParent.args if pargs.templatemode == "nocats" then args.nocat = "yes" end args.alternative = pargs.alternative return frame:expandTemplate{title="Unmaintained extension",args=args} end end function p.maintenanceLinks(frame) local base = frame:expandTemplate{title="translatable"} if base == mw.title.getCurrentTitle.prefixedText then return end local out = "" local content = mw.title.new(base):getContent -- Check if the source page was archived or not if content:find("{{[aA]rchived ?[Ee]xtension") or content:find("{{TNT|[Aa]rchived ?[Ee]xtension") then return " Template:Extension/archived " --Check if the source page was deleted or not elseif content:find("{{[dD]eleted extension security warning") then return " Template:Extension/vulnerabilities " end end function p.isOnGerrit(frame) local title = mw.title.getCurrentTitle if not title:inNamespace("Extension") and not title:inNamespace("Skin") then return "n/a" end local base = frame:expandTemplate{title="translatable"} local content = mw.title.new(base):getContent if content:find("{{WikimediaDownload") or content:find("TNT|WikimediaDownload") or content:find("|repo%s*=") then return "yes" end end

-- -- -- Get a category if the extension isn't in Module:ExtensionJson. -- function p.getExtensionJsonCategory( frame ) if getExtData == nil then return cat( 'Extensions not in ExtensionJson' ) end end

-- -- -- Get number of downloads this quarter function p.getDownloads( frame ) local data = getPopularityData if data == nil or data.downloads == nil then return '' end -- TODO: Change it to includ the rank as well, perhaps like: 12 (2nd). return mw.getContentLanguage:formatNum( data.downloads['13w'] ) end

-- -- -- Get number of downloads this quarter function p.getPublicSites( frame ) local data = getPopularityData if data == nil or data.siteCount == nil then return '' end -- TODO: Change it to includ the rank as well, perhaps like: 12 (2nd). return mw.getContentLanguage:formatNum( data.siteCount ) end

return p