Jump to content

Module:Portal

Permanently protected module
From Wikipedia, the free encyclopedia

--[==[ This module is a Lua implementation of the old {{Portal}} template. As of February 2019 it is used on nearly 7,900,000 articles.-- Please take care when updating it! It outputs two functions: p.portal, which generates a list of portals, and p.image, which-- produces the image name for an individual portal.-- The portal image data is kept in submodules of [[Module:Portal/images]], listed below:-- [[Module:Portal/images/a]] - for portal names beginning with "A".-- [[Module:Portal/images/b]] - for portal names beginning with "B".-- [[Module:Portal/images/c]] - for portal names beginning with "C".-- [[Module:Portal/images/d]] - for portal names beginning with "D".-- [[Module:Portal/images/e]] - for portal names beginning with "E".-- [[Module:Portal/images/f]] - for portal names beginning with "F".-- [[Module:Portal/images/g]] - for portal names beginning with "G".-- [[Module:Portal/images/h]] - for portal names beginning with "H".-- [[Module:Portal/images/i]] - for portal names beginning with "I".-- [[Module:Portal/images/j]] - for portal names beginning with "J".-- [[Module:Portal/images/k]] - for portal names beginning with "K".-- [[Module:Portal/images/l]] - for portal names beginning with "L".-- [[Module:Portal/images/m]] - for portal names beginning with "M".-- [[Module:Portal/images/n]] - for portal names beginning with "N".-- [[Module:Portal/images/o]] - for portal names beginning with "O".-- [[Module:Portal/images/p]] - for portal names beginning with "P".-- [[Module:Portal/images/q]] - for portal names beginning with "Q".-- [[Module:Portal/images/r]] - for portal names beginning with "R".-- [[Module:Portal/images/s]] - for portal names beginning with "S".-- [[Module:Portal/images/t]] - for portal names beginning with "T".-- [[Module:Portal/images/u]] - for portal names beginning with "U".-- [[Module:Portal/images/v]] - for portal names beginning with "V".-- [[Module:Portal/images/w]] - for portal names beginning with "W".-- [[Module:Portal/images/x]] - for portal names beginning with "X".-- [[Module:Portal/images/y]] - for portal names beginning with "Y".-- [[Module:Portal/images/z]] - for portal names beginning with "Z".-- [[Module:Portal/images/other]] - for portal names beginning with any other letters. This includes numbers,-- letters with diacritics, and letters in non-Latin alphabets.-- [[Module:Portal/images/aliases]] - for adding aliases for existing portal names. Use this page for variations-- in spelling and diacritics, etc., no matter what letter the portal begins with.---- The images data pages are separated by the first letter to reduce server load when images are added, changed, or removed.-- Previously all the images were on one data page at [[Module:Portal/images]], but this had the disadvantage that all-- 5,000,000 pages using this module needed to be refreshed every time an image was added or removed.]==]localp={}-- determine whether we're being called from a sandboxlocalisSandbox=mw.getCurrentFrame():getTitle():find('sandbox',1,true)localsandbox=isSandboxand'/sandbox'or''localfunctionsandboxVersion(s)returnisSandboxands..'-sand'orsendlocaltemplatestyles='Module:Portal'..sandbox..'/styles.css'localgetArgs=require('Module:Arguments').getArgslocalyesno=require('Module:Yesno')-- List of non-talk namespaces which should not be tracked (Talk pages are never tracked)localbadNamespaces={'user','template','draft','wikipedia'}-- Check whether to do tracking in this namespace-- Returns true unless the page is one of the banned namespaceslocalfunctioncheckTracking(title)localthisPage=titleormw.title.getCurrentTitle()ifthisPage.isTalkPagethenreturnfalseendlocalns=thisPage.nsText:lower()for_,vinipairs(badNamespaces)doifns==vthenreturnfalseendendreturntrueendlocalfunctionmatchImagePage(s)-- Finds the appropriate image subpage given a lower-case-- portal name plus the first letter of that portal name.iftype(s)~='string'or#s<1thenreturnendlocalfirstLetter=mw.ustring.sub(s,1,1)localimagePageifmw.ustring.find(firstLetter,'^[a-z]')thenimagePage='Module:Portal/images/'..firstLetter..sandboxelseimagePage='Module:Portal/images/other'..sandboxendreturnmw.loadData(imagePage)[s]endlocalfunctiongetAlias(s)-- Gets an alias from the image alias data page.localaliasData=mw.loadData('Module:Portal/images/aliases'..sandbox)forportal,aliasesinpairs(aliasData)dofor_,aliasinipairs(aliases)doifalias==sthenreturnportalendendendendlocaldefaultImage='Portal-puzzle.svg|link=|alt='localfunctiongetImageName(s)-- Gets the image name for a given string.iftype(s)~='string'or#s<1thenreturndefaultImageends=mw.ustring.lower(s)localimage=matchImagePage(s)ormatchImagePage(getAlias(s))ordefaultImageimage=mw.ustring.gsub(image,'^File:','')--- strip mistaken leading File: or Image:image=mw.ustring.gsub(image,'^Image:','')returnimageendlocalfunctionexists(title)localsuccess,exists=pcall(function()returntitle.existsend)-- If success = false, then we're out of expensive parser function calls and can't check whether it exists-- in that case, don't throw a Lua errorreturnnotsuccessorexistsend-- Function to check argument portals for errors, generate tracking categories if needed-- Function first checks for too few/many portals provided-- Then checks the portal list to purge any portals that don't exist-- Arguments:-- portals: raw list of portals-- args.tracking: is tracking requested? (will not track on bad titles or namespaces)-- args.redlinks: should redlinks be displayed?-- args.minPortals: minimum number of portal arguments-- args.maxPortals: maximum number of portal arguments-- Returns:-- portals = list of portals, with redlinks purged (if args.redlinks=false)-- trackingCat = possible tracking category-- errorMsg = error messagefunctionp._checkPortals(portals,args)localtrackingCat=''localerrMsg=nil-- Tracking is on by default.-- It is disabled if any of the following is true-- 1/ the parameter "tracking" is set to 'no, 'n', or 'false'-- 2/ the current page fails the namespace or pagename tests localtrackingEnabled=args.trackingandcheckTracking()args.minPortals=args.minPortalsor1args.maxPortals=args.maxPortalsor-1-- check for too few portalsif#portals<args.minPortalsthenerrMsg='please specify at least '..args.minPortals..' portal'..(args.minPortals>1and's'or'')trackingCat=(trackingEnabledand'[[Category:Portal templates with too few portals]]'or'')returnportals,trackingCat,errMsgend-- check for too many portalsifargs.maxPortals>=0and#portals>args.maxPortalsthenerrMsg='too many portals (maximum = '..args.maxPortals..')'trackingCat=(trackingEnabledand'[[Category:Portal templates with too many portals]]'or'')returnportals,trackingCat,errMsgendifnotargs.redlinksortrackingEnabledthen-- make new list of portals that existlocalexistingPortals={}for_,portalinipairs(portals)dolocalportalTitle=mw.title.new(portal,"Portal")-- if portal exists, put it into listifportalTitleandexists(portalTitle)thentable.insert(existingPortals,portal)-- otherwise set tracking catelseiftrackingEnabledthentrackingCat="[[Category:Portal templates with redlinked portals]]"endend-- If redlinks is off, use portal list purged of redlinksportals=args.redlinksandportalsorexistingPortals-- if nothing left after purge, set tracking catif#portals==0andtrackingEnabledthentrackingCat=trackingCat.."[[Category:Pages with empty portal template]]"endendreturnportals,trackingCat,errMsgendlocalfunctionportalBox(args)returnmw.html.create('ul'):attr('role','navigation'):attr('aria-label','Portals'):addClass('noprint'):addClass(args.errorand''orsandboxVersion('portalbox')):addClass(args.borderandsandboxVersion('portalborder')or''):addClass(sandboxVersion(args.leftand'portalleft'or'portalright')):css('margin',args.marginornil):newline()endlocalfunctionfillBox(root,contents)for_,iteminipairs(contents)dolocalentry=root:tag('li')entry:addClass(sandboxVersion('portalbox-entry'))localimage=entry:tag('span')image:addClass(sandboxVersion('portalbox-image'))image:wikitext(item[1])locallink=entry:tag('span')link:addClass(sandboxVersion('portalbox-link'))link:wikitext(item[2])endreturnrootendfunctionp._portal(portals,args)-- This function builds the portal box used by the {{portal}} template.-- Normalize all argumentsifargs.redlinks=='include'thenargs.redlinks=trueendargs.addBreak=args['break']forkey,defaultinpairs({left=false,tracking=true,nominimum=false,redlinks=false,addBreak=false,border=true})doifargs[key]==nilthenargs[key]=defaultendargs[key]=yesno(args[key],default)endlocalroot=portalBox(args)localtrackingCat=''localerrMsg=nilargs.minPortals=args.nominimumand0or1args.maxPortals=-1portals,trackingCat,errMsg=p._checkPortals(portals,args)root:wikitext(trackingCat)-- if error message, put it in the box and returniferrMsgthenifargs.borderthen-- suppress error message when border=noargs.error=true-- recreate box without fancy formattingroot=portalBox(args)root:wikitext(trackingCat)localerrTag=root:tag('strong')errTag:addClass('error')errTag:css('padding','0.2em')errTag:wikitext('Error: '..errMsg)endreturntostring(root)end-- if no portals (and no error), just return tracking categoryif#portals==0thenreturntrackingCatendlocalcontents={}-- Display the portals specified in the positional arguments.localdefaultUsed=nilfor_,portalinipairs(portals)dolocalportalImage=getImageName(portal)ifportalImage==defaultImagethendefaultUsed=portalendlocalimage=string.format('[[File:%s|32x28px|class=noviewer]]',portalImage)locallink=string.format('[[Portal:%s|%s%sportal]]',portal,portal,args.addBreakand'<br />'or' ')table.insert(contents,{image,link})endifdefaultUsedandargs.trackingandcheckTracking()thenlocalcat=string.format('[[Category:Portal templates with default image|%s]]',defaultUsed)root:wikitext(cat)endreturntostring(fillBox(root,contents))endfunctionp._demo(imageList,args)forkey,defaultinpairs({left=false,border=true})doifargs[key]==nilthenargs[key]=defaultendargs[key]=yesno(args[key],default)endlocalroot=portalBox(args)localcontents={}-- Display the portals specified in the positional arguments.for_,fninipairs(imageList)dolocalimage=string.format('[[File:%s|32x28px|class=noviewer]]',fn)locallink=string.format('[[:File:%s|%s]]',fn,fn)table.insert(contents,{image,link})endreturntostring(fillBox(root,contents))endfunctionp._image(portal,keep)-- Wrapper function to allow getImageName() to be accessed through #invoke.-- backward compatibility: if table passed, take first elementiftype(portal)=='table'thenportal=portal[1]endlocalname=getImageName(portal)-- If keep is yes (or equivalent), then allow all metadata (like image borders) to be returnedlocalkeepargs=yesno(keep)localargs=mw.text.split(name,"|",true)localresult={args[1]}-- the filename always comes firstlocalcategory=''-- parse name, looking for category argumentsfori=2,#argsdolocalm=mw.ustring.match(args[i],"^%s*category%s*=")ifkeepargsormthentable.insert(result,args[i])endend-- reassemble argumentsreturntable.concat(result,"|")endlocalfunctiongetAllImageTable()-- Returns an array containing all image subpages (minus aliases) as loaded by mw.loadData.localimages={}fori,subpageinipairs{'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','other'}dolocalimageTable=mw.loadData('Module:Portal/images/'..subpage..sandbox)forportal,imageinpairs(imageTable)dolocalargs=mw.text.split(image,"|")images[portal]=args[1]-- just use image filenameendendreturnimagesendfunctionp._displayAll(portals,args)-- This function displays all portals that have portal images. This function is for maintenance purposes and should not be used in-- articles, for two reasons: 1) there are over 1500 portals with portal images, and 2) the module doesn't record how the portal-- names are capitalized, so the portal links may be broken.locallang=mw.language.getContentLanguage()portals=portalsor{}forportalinpairs(getAllImageTable())dotable.insert(portals,lang:ucfirst(portal))endtable.sort(portals)args.redlinks=args.redlinksor"yes"returnp._portal(portals,args)endfunctionp._imageDupes()-- This function searches the image subpages to find duplicate images. If duplicate images exist, it is not necessarily a bad thing,-- as different portals might just happen to choose the same image. However, this function is helpful in identifying images that-- should be moved to a portal alias for ease of maintenance.localexists,dupes={},{}forportal,imageinpairs(getAllImageTable())doifnotexists[image]thenexists[image]=portalelsetable.insert(dupes,string.format('The image "[[:File:%s|%s]]" is used for both portals "%s" and "%s".',image,image,exists[image],portal))endendif#dupes<1thenreturn'No duplicate images found.'elsereturn'The following duplicate images were found:\n* '..table.concat(dupes,'\n* ')endendlocalfunctionprocessPortalArgs(args)-- This function processes a table of arguments and returns two tables: an array of portal names for processing by ipairs, and a table of-- the named arguments that specify style options, etc. We need to use ipairs because we want to list all the portals in the order-- they were passed to the template, but we also want to be able to deal with positional arguments passed explicitly, for example-- {{portal|2=Politics}}. The behaviour of ipairs is undefined if nil values are present, so we need to make sure they are all removed.args=type(args)=='table'andargsor{}localportals={}localnamedArgs={}fork,vinpairs(args)doiftype(k)=='number'andtype(v)=='string'then-- Make sure we have no non-string portal names.table.insert(portals,k)elseiftype(k)~='number'thennamedArgs[k]=vendendtable.sort(portals)fori,vinipairs(portals)doportals[i]=args[v]endreturnportals,namedArgsend-- Entry point for sorting portals from other named argumentsfunctionp._processPortalArgs(args)returnprocessPortalArgs(args)endfunctionp.image(frame)localorigArgs=getArgs(frame)localportals,args=processPortalArgs(origArgs)returnp._image(portals[1],args.border)endfunctionp.demo(frame)localargs=getArgs(frame)localstyles=frame:extensionTag{name='templatestyles',args={src=templatestyles}}returnstyles..p._demo(args,args)endlocalfunctionmakeWrapper(funcName)-- Processes external arguments and sends them to the other functions.returnfunction(frame)-- If called via #invoke, use the args passed into the invoking-- template, or the args passed to #invoke if any exist. Otherwise-- assume args are being passed directly in from the debug console-- or from another Lua module. -- Also: trim whitespace and remove blank argumentslocalorigArgs=getArgs(frame)-- create two tables to pass to func: an array of portal names, and a table of named arguments.localportals,args=processPortalArgs(origArgs)localresults=''iffuncName=='_portal'orfuncName=='_displayAll'thenresults=frame:extensionTag{name='templatestyles',args={src=templatestyles}}endreturnresults..p[funcName](portals,args)endendfor_,funcNameinipairs{'portal','imageDupes','displayAll'}dop[funcName]=makeWrapper('_'..funcName)endreturnp
close