Permanently protected module
From Wikipedia, the free encyclopedia


-- This module implements {{pp-meta}} and its daughter templates such as

-- {{pp-dispute}}, {{pp-vandalism}} and {{pp-sock}}.



-- Initialise necessary modules.

require('strict')

local makeFileLink = require('Module:File link')._main

local effectiveProtectionLevel = require('Module:Effective protection level')._main

local effectiveProtectionExpiry = require('Module:Effective protection expiry')._main

local yesno = require('Module:Yesno')



-- Lazily initialise modules and objects we don't always need.

local getArgs, makeMessageBox, lang



-- Set constants.

local CONFIG_MODULE = 'Module:Protection banner/config'



--------------------------------------------------------------------------------

-- Helper functions

--------------------------------------------------------------------------------



local function makeCategoryLink(cat, sort)

	if cat then

		return string.format(

			'[[%s:%s|%s]]',

			mw.site.namespaces14].name,

			cat,

			sort

		)

	end

end



-- Validation function for the expiry and the protection date

local function validateDate(dateString, dateType)

	if not lang then

		lang = mw.language.getContentLanguage()

	end

	local success, result = pcall(lang.formatDate, lang, 'U', dateString)

	if success then

		result = tonumber(result)

		if result then

			return result

		end

	end

	error(string.format(

		'invalid %s: %s',

		dateType,

		tostring(dateString)

	), 4)

end



local function makeFullUrl(page, query, display)

	return string.format(

		'[%s %s]',

		tostring(mw.uri.fullUrl(page, query)),

		display

	)

end



-- Given a directed graph formatted as node -> table of direct successors,

-- get a table of all nodes reachable from a given node (though always

-- including the given node).

local function getReachableNodes(graph, start)

	local toWalk, retval = {[start = true}, {}

	while true do

		-- Can't use pairs() since we're adding and removing things as we're iterating

		local k = next(toWalk) -- This always gets the "first" key

		if k == nil then

			return retval

		end

		toWalkk = nil

		retvalk = true

		for _,v in ipairs(graphk]) do

			if not retvalv then

				toWalkv = true

			end

		end

	end

end



--------------------------------------------------------------------------------

-- Protection class

--------------------------------------------------------------------------------



local Protection = {}

Protection.__index = Protection



Protection.supportedActions = {

	 = true,

	move = true,

	autoreview = true,

	upload = true

}



Protection.bannerConfigFields = {

	'text',

	'explanation',

	'tooltip',

	'alt',

	'link',

	'image'

}



function Protection.new(args, cfg, title)

	local obj = {}

	obj._cfg = cfg

	obj.title = title or mw.title.getCurrentTitle()



	-- Set action

	if not args.action then

		obj.action = 'edit'

	elseif Protection.supportedActionsargs.action then

		obj.action = args.action

	else

		error(string.format(

			'invalid action: %s',

			tostring(args.action)

		), 3)

	end



	-- Set level

	obj.level = args.demolevel or effectiveProtectionLevel(obj.action, obj.title)

	if not obj.level or (obj.action == 'move' and obj.level == 'autoconfirmed') then

		-- Users need to be autoconfirmed to move pages anyway, so treat

		-- semi-move-protected pages as unprotected.

		obj.level = '*'

	end



	-- Set expiry

	local effectiveExpiry = effectiveProtectionExpiry(obj.action, obj.title)

	if effectiveExpiry == 'infinity' then

		obj.expiry = 'indef'

	elseif effectiveExpiry ~= 'unknown' then

		obj.expiry = validateDate(effectiveExpiry, 'expiry date')

	end



	-- Set reason

	if args1 then

		obj.reason = mw.ustring.lower(args1])

		if obj.reason:find('|') then

			error('reasons cannot contain the pipe character ("|")', 3)

		end

	end



	-- Set protection date

	if args.date then

		obj.protectionDate = validateDate(args.date, 'protection date')

	end

	

	-- Set banner config

	do

		obj.bannerConfig = {}

		local configTables = {}

		if cfg.bannersobj.action then

			configTables#configTables + 1 = cfg.bannersobj.action][obj.reason

		end

		if cfg.defaultBannersobj.action then

			configTables#configTables + 1 = cfg.defaultBannersobj.action][obj.level

			configTables#configTables + 1 = cfg.defaultBannersobj.action].default

		end

		configTables#configTables + 1 = cfg.masterBanner

		for i, field in ipairs(Protection.bannerConfigFields) do

			for j, t in ipairs(configTables) do

				if tfield then

					obj.bannerConfigfield = tfield

					break

				end

			end

		end

	end

	return setmetatable(obj, Protection)

end



function Protection:isUserScript()

	-- Whether the page is a user JavaScript or CSS page.

	local title = self.title

	return title.namespace == 2 and (

		title.contentModel == 'javascript' or title.contentModel == 'css'

	)

end



function Protection:isProtected()

	return self.level ~= '*'

end



function Protection:shouldShowLock()

	-- Whether we should output a banner/padlock

	return self:isProtected() and not self:isUserScript()

end



-- Whether this page needs a protection category.

Protection.shouldHaveProtectionCategory = Protection.shouldShowLock



function Protection:isTemporary()

	return type(self.expiry) == 'number'

end



function Protection:makeProtectionCategory()

	if not self:shouldHaveProtectionCategory() then

		return ''

	end



	local cfg = self._cfg

	local title = self.title

	

	-- Get the expiry key fragment.

	local expiryFragment

	if self.expiry == 'indef' then

		expiryFragment = self.expiry

	elseif type(self.expiry) == 'number' then

		expiryFragment = 'temp'

	end



	-- Get the namespace key fragment.

	local namespaceFragment = cfg.categoryNamespaceKeystitle.namespace

	if not namespaceFragment and title.namespace % 2 == 1 then

			namespaceFragment = 'talk'

	end



	-- Define the order that key fragments are tested in. This is done with an

	-- array of tables containing the value to be tested, along with its

	-- position in the cfg.protectionCategories table.

	local order = {

		{val = expiryFragment,    keypos = 1},

		{val = namespaceFragment, keypos = 2},

		{val = self.reason,       keypos = 3},

		{val = self.level,        keypos = 4},

		{val = self.action,       keypos = 5}

	}



	--[[

	-- The old protection templates used an ad-hoc protection category system,

	-- with some templates prioritising namespaces in their categories, and

	-- others prioritising the protection reason. To emulate this in this module

	-- we use the config table cfg.reasonsWithNamespacePriority to set the

	-- reasons for which namespaces have priority over protection reason.

	-- If we are dealing with one of those reasons, move the namespace table to

	-- the end of the order table, i.e. give it highest priority. If not, the

	-- reason should have highest priority, so move that to the end of the table

	-- instead.

	--]]

	table.insert(order, table.remove(order, self.reason and cfg.reasonsWithNamespacePriorityself.reason and 2 or 3))

 

	--[[

	-- Define the attempt order. Inactive subtables (subtables with nil "value"

	-- fields) are moved to the end, where they will later be given the key

	-- "all". This is to cut down on the number of table lookups in

	-- cfg.protectionCategories, which grows exponentially with the number of

	-- non-nil keys. We keep track of the number of active subtables with the

	-- noActive parameter.

	--]]

	local noActive, attemptOrder

	do

		local active, inactive = {}, {}

		for i, t in ipairs(order) do

			if t.val then

				active#active + 1 = t

			else

				inactive#inactive + 1 = t

			end

		end

		noActive = #active

		attemptOrder = active

		for i, t in ipairs(inactive) do

			attemptOrder#attemptOrder + 1 = t

		end

	end

 

	--[[

	-- Check increasingly generic key combinations until we find a match. If a

	-- specific category exists for the combination of key fragments we are

	-- given, that match will be found first. If not, we keep trying different

	-- key fragment combinations until we match using the key

	-- "all-all-all-all-all".

	--

	-- To generate the keys, we index the key subtables using a binary matrix

	-- with indexes i and j. j is only calculated up to the number of active

	-- subtables. For example, if there were three active subtables, the matrix

	-- would look like this, with 0 corresponding to the key fragment "all", and

	-- 1 corresponding to other key fragments.

	-- 

	--   j 1  2  3

	-- i  

	-- 1   1  1  1

	-- 2   0  1  1

	-- 3   1  0  1

	-- 4   0  0  1

	-- 5   1  1  0

	-- 6   0  1  0

	-- 7   1  0  0

	-- 8   0  0  0

	-- 

	-- Values of j higher than the number of active subtables are set

	-- to the string "all".

	--

	-- A key for cfg.protectionCategories is constructed for each value of i.

	-- The position of the value in the key is determined by the keypos field in

	-- each subtable.

	--]]

	local cats = cfg.protectionCategories

	for i = 1, 2^noActive do

		local key = {}

		for j, t in ipairs(attemptOrder) do

			if j > noActive then

				keyt.keypos = 'all'

			else

				local quotient = i / 2 ^ (j - 1)

				quotient = math.ceil(quotient)

				if quotient % 2 == 1 then

					keyt.keypos = t.val

				else

					keyt.keypos = 'all'

				end

			end

		end

		key = table.concat(key, '|')

		local attempt = catskey

		if attempt then

			return makeCategoryLink(attempt, title.text)

		end

	end

	return ''

end



function Protection:isIncorrect()

	local expiry = self.expiry

	return not self:shouldHaveProtectionCategory()

		or type(expiry) == 'number' and expiry < os.time()

end



function Protection:isTemplateProtectedNonTemplate()

	local action, namespace = self.action, self.title.namespace

	return self.level == 'templateeditor'

		and (

			(action ~= 'edit' and action ~= 'move')

			or (namespace ~= 10 and namespace ~= 828)

		)

end



function Protection:makeCategoryLinks()

	local msg = self._cfg.msg

	local ret = {self:makeProtectionCategory()}

	if self:isIncorrect() then

		ret#ret + 1 = makeCategoryLink(

			msg'tracking-category-incorrect'],

			self.title.text

		)

	end

	if self:isTemplateProtectedNonTemplate() then

		ret#ret + 1 = makeCategoryLink(

			msg'tracking-category-template'],

			self.title.text

		)

	end

	return table.concat(ret)

end



--------------------------------------------------------------------------------

-- Blurb class

--------------------------------------------------------------------------------



local Blurb = {}

Blurb.__index = Blurb



Blurb.bannerTextFields = {

	text = true,

	explanation = true,

	tooltip = true,

	alt = true,

	link = true

}



function Blurb.new(protectionObj, args, cfg)

	return setmetatable({

		_cfg = cfg,

		_protectionObj = protectionObj,

		_args = args

	}, Blurb)

end



-- Private methods --



function Blurb:_formatDate(num)

	-- Formats a Unix timestamp into dd Month, YYYY format.

	lang = lang or mw.language.getContentLanguage()

	local success, date = pcall(

		lang.formatDate,

		lang,

		self._cfg.msg'expiry-date-format' or 'j F Y',

		'@' .. tostring(num)

	)

	if success then

		return date

	end

end



function Blurb:_getExpandedMessage(msgKey)

	return self:_substituteParameters(self._cfg.msgmsgKey])

end



function Blurb:_substituteParameters(msg)

	if not self._params then

		local parameterFuncs = {}



		parameterFuncs.CURRENTVERSION     = self._makeCurrentVersionParameter

		parameterFuncs.EDITREQUEST        = self._makeEditRequestParameter

		parameterFuncs.EXPIRY             = self._makeExpiryParameter

		parameterFuncs.EXPLANATIONBLURB   = self._makeExplanationBlurbParameter

		parameterFuncs.IMAGELINK          = self._makeImageLinkParameter

		parameterFuncs.INTROBLURB         = self._makeIntroBlurbParameter

		parameterFuncs.INTROFRAGMENT      = self._makeIntroFragmentParameter

		parameterFuncs.PAGETYPE           = self._makePagetypeParameter

		parameterFuncs.PROTECTIONBLURB    = self._makeProtectionBlurbParameter

		parameterFuncs.PROTECTIONDATE     = self._makeProtectionDateParameter

		parameterFuncs.PROTECTIONLEVEL    = self._makeProtectionLevelParameter

		parameterFuncs.PROTECTIONLOG      = self._makeProtectionLogParameter

		parameterFuncs.TALKPAGE           = self._makeTalkPageParameter

		parameterFuncs.TOOLTIPBLURB       = self._makeTooltipBlurbParameter

		parameterFuncs.TOOLTIPFRAGMENT    = self._makeTooltipFragmentParameter

		parameterFuncs.VANDAL             = self._makeVandalTemplateParameter

		

		self._params = setmetatable({}, {

			__index = function (t, k)

				local param

				if parameterFuncsk then

					param = parameterFuncsk](self)

				end

				param = param or ''

				tk = param

				return param

			end

		})

	end

	

	msg = msg:gsub('${(%u+)}', self._params)

	return msg

end



function Blurb:_makeCurrentVersionParameter()

	-- A link to the page history or the move log, depending on the kind of

	-- protection.

	local pagename = self._protectionObj.title.prefixedText

	if self._protectionObj.action == 'move' then

		-- We need the move log link.

		return makeFullUrl(

			'Special:Log',

			{type = 'move', page = pagename},

			self:_getExpandedMessage('current-version-move-display')

		)

	else

		-- We need the history link.

		return makeFullUrl(

			pagename,

			{action = 'history'},

			self:_getExpandedMessage('current-version-edit-display')

		)

	end

end



function Blurb:_makeEditRequestParameter()

	local mEditRequest = require('Module:Submit an edit request')

	local action = self._protectionObj.action

	local level = self._protectionObj.level

	

	-- Get the edit request type.

	local requestType

	if action == 'edit' then

		if level == 'autoconfirmed' then

			requestType = 'semi'

		elseif level == 'extendedconfirmed' then

			requestType = 'extended'

		elseif level == 'templateeditor' then

			requestType = 'template'

		end

	end

	requestType = requestType or 'full'

	

	-- Get the display value.

	local display = self:_getExpandedMessage('edit-request-display')



	return mEditRequest._link{type = requestType, display = display}

end



function Blurb:_makeExpiryParameter()

	local expiry = self._protectionObj.expiry

	if type(expiry) == 'number' then

		return self:_formatDate(expiry)

	else

		return expiry

	end

end



function Blurb:_makeExplanationBlurbParameter()

	-- Cover special cases first.

	if self._protectionObj.title.namespace == 8 then

		-- MediaWiki namespace

		return self:_getExpandedMessage('explanation-blurb-nounprotect')

	end



	-- Get explanation blurb table keys

	local action = self._protectionObj.action

	local level = self._protectionObj.level

	local talkKey = self._protectionObj.title.isTalkPage and 'talk' or 'subject'



	-- Find the message in the explanation blurb table and substitute any

	-- parameters.

	local explanations = self._cfg.explanationBlurbs

	local msg

	if explanationsaction][level and explanationsaction][level][talkKey then

		msg = explanationsaction][level][talkKey

	elseif explanationsaction][level and explanationsaction][level].default then

		msg = explanationsaction][level].default

	elseif explanationsaction].default and explanationsaction].defaulttalkKey then

		msg = explanationsaction].defaulttalkKey

	elseif explanationsaction].default and explanationsaction].default.default then

		msg = explanationsaction].default.default

	else

		error(string.format(

			'could not find explanation blurb for action "%s", level "%s" and talk key "%s"',

			action,

			level,

			talkKey

		), 8)

	end

	return self:_substituteParameters(msg)

end



function Blurb:_makeImageLinkParameter()

	local imageLinks = self._cfg.imageLinks

	local action = self._protectionObj.action

	local level = self._protectionObj.level

	local msg

	if imageLinksaction][level then

		msg = imageLinksaction][level

	elseif imageLinksaction].default then

		msg = imageLinksaction].default

	else

		msg = imageLinks..default

	end

	return self:_substituteParameters(msg)

end



function Blurb:_makeIntroBlurbParameter()

	if self._protectionObj:isTemporary() then

		return self:_getExpandedMessage('intro-blurb-expiry')

	else

		return self:_getExpandedMessage('intro-blurb-noexpiry')

	end

end



function Blurb:_makeIntroFragmentParameter()

	if self._protectionObj:isTemporary() then

		return self:_getExpandedMessage('intro-fragment-expiry')

	else

		return self:_getExpandedMessage('intro-fragment-noexpiry')

	end

end



function Blurb:_makePagetypeParameter()

	local pagetypes = self._cfg.pagetypes

	return pagetypesself._protectionObj.title.namespace

		or pagetypes.default

		or error('no default pagetype defined', 8)

end



function Blurb:_makeProtectionBlurbParameter()

	local protectionBlurbs = self._cfg.protectionBlurbs

	local action = self._protectionObj.action

	local level = self._protectionObj.level

	local msg

	if protectionBlurbsaction][level then

		msg = protectionBlurbsaction][level

	elseif protectionBlurbsaction].default then

		msg = protectionBlurbsaction].default

	elseif protectionBlurbs..default then

		msg = protectionBlurbs..default

	else

		error('no protection blurb defined for protectionBlurbs.edit.default', 8)

	end

	return self:_substituteParameters(msg)

end



function Blurb:_makeProtectionDateParameter()

	local protectionDate = self._protectionObj.protectionDate

	if type(protectionDate) == 'number' then

		return self:_formatDate(protectionDate)

	else

		return protectionDate

	end

end



function Blurb:_makeProtectionLevelParameter()

	local protectionLevels = self._cfg.protectionLevels

	local action = self._protectionObj.action

	local level = self._protectionObj.level

	local msg

	if protectionLevelsaction][level then

		msg = protectionLevelsaction][level

	elseif protectionLevelsaction].default then

		msg = protectionLevelsaction].default

	elseif protectionLevels..default then

		msg = protectionLevels..default

	else

		error('no protection level defined for protectionLevels.edit.default', 8)

	end

	return self:_substituteParameters(msg)

end



function Blurb:_makeProtectionLogParameter()

	local pagename = self._protectionObj.title.prefixedText

	if self._protectionObj.action == 'autoreview' then

		-- We need the pending changes log.

		return makeFullUrl(

			'Special:Log',

			{type = 'stable', page = pagename},

			self:_getExpandedMessage('pc-log-display')

		)

	else

		-- We need the protection log.

		return makeFullUrl(

			'Special:Log',

			{type = 'protect', page = pagename},

			self:_getExpandedMessage('protection-log-display')

		)

	end

end



function Blurb:_makeTalkPageParameter()

	return string.format(

		'[[%s:%s#%s|%s]]',

		mw.site.namespacesself._protectionObj.title.namespace].talk.name,

		self._protectionObj.title.text,

		self._args.section or 'top',

		self:_getExpandedMessage('talk-page-link-display')

	)

end



function Blurb:_makeTooltipBlurbParameter()

	if self._protectionObj:isTemporary() then

		return self:_getExpandedMessage('tooltip-blurb-expiry')

	else

		return self:_getExpandedMessage('tooltip-blurb-noexpiry')

	end

end



function Blurb:_makeTooltipFragmentParameter()

	if self._protectionObj:isTemporary() then

		return self:_getExpandedMessage('tooltip-fragment-expiry')

	else

		return self:_getExpandedMessage('tooltip-fragment-noexpiry')

	end

end



function Blurb:_makeVandalTemplateParameter()

	return mw.getCurrentFrame():expandTemplate{

		title="vandal-m",

		args={self._args.user or self._protectionObj.title.baseText}

	}

end



-- Public methods --



function Blurb:makeBannerText(key)

	-- Validate input.

	if not key or not Blurb.bannerTextFieldskey then

		error(string.format(

			'"%s" is not a valid banner config field',

			tostring(key)

		), 2)

	end



	-- Generate the text.

	local msg = self._protectionObj.bannerConfigkey

	if type(msg) == 'string' then

		return self:_substituteParameters(msg)

	elseif type(msg) == 'function' then

		msg = msg(self._protectionObj, self._args)

		if type(msg) ~= 'string' then

			error(string.format(

				'bad output from banner config function with key "%s"'

					.. ' (expected string, got %s)',

				tostring(key),

				type(msg)

			), 4)

		end

		return self:_substituteParameters(msg)

	end

end



--------------------------------------------------------------------------------

-- BannerTemplate class

--------------------------------------------------------------------------------



local BannerTemplate = {}

BannerTemplate.__index = BannerTemplate



function BannerTemplate.new(protectionObj, cfg)

	local obj = {}

	obj._cfg = cfg



	-- Set the image filename.

	local imageFilename = protectionObj.bannerConfig.image

	if imageFilename then

		obj._imageFilename = imageFilename

	else

		-- If an image filename isn't specified explicitly in the banner config,

		-- generate it from the protection status and the namespace.

		local action = protectionObj.action

		local level = protectionObj.level

		local namespace = protectionObj.title.namespace

		local reason = protectionObj.reason

		

		-- Deal with special cases first.

		if (

			namespace == 10

			or namespace == 828

			or reason and obj._cfg.indefImageReasonsreason

			)

			and action == 'edit'

			and level == 'sysop'

			and not protectionObj:isTemporary()

		then

			-- Fully protected modules and templates get the special red "indef"

			-- padlock.

			obj._imageFilename = obj._cfg.msg'image-filename-indef'

		else

			-- Deal with regular protection types.

			local images = obj._cfg.images

			if imagesaction then

				if imagesaction][level then

					obj._imageFilename = imagesaction][level

				elseif imagesaction].default then

					obj._imageFilename = imagesaction].default

				end

			end

		end

	end

	return setmetatable(obj, BannerTemplate)

end



function BannerTemplate:renderImage()

	local filename = self._imageFilename

		or self._cfg.msg'image-filename-default'

		or 'Transparent.gif'

	return makeFileLink{

		file = filename,

		size = (self.imageWidth or 20) .. 'px',

		alt = self._imageAlt,

		link = self._imageLink,

		caption = self.imageCaption

	}

end



--------------------------------------------------------------------------------

-- Banner class

--------------------------------------------------------------------------------



local Banner = setmetatable({}, BannerTemplate)

Banner.__index = Banner



function Banner.new(protectionObj, blurbObj, cfg)

	local obj = BannerTemplate.new(protectionObj, cfg) -- This doesn't need the blurb.

	obj.imageWidth = 40

	obj.imageCaption = blurbObj:makeBannerText('alt') -- Large banners use the alt text for the tooltip.

	obj._reasonText = blurbObj:makeBannerText('text')

	obj._explanationText = blurbObj:makeBannerText('explanation')

	obj._page = protectionObj.title.prefixedText -- Only makes a difference in testing.

	return setmetatable(obj, Banner)

end



function Banner:__tostring()

	-- Renders the banner.

	makeMessageBox = makeMessageBox or require('Module:Message box').main

	local reasonText = self._reasonText or error('no reason text set', 2)

	local explanationText = self._explanationText

	local mbargs = {

		page = self._page,

		type = 'protection',

		image = self:renderImage(),

		text = string.format(

			"'''%s'''%s",

			reasonText,

			explanationText and '<br />' .. explanationText or ''

		)

	}

	return makeMessageBox('mbox', mbargs)

end



--------------------------------------------------------------------------------

-- Padlock class

--------------------------------------------------------------------------------



local Padlock = setmetatable({}, BannerTemplate)

Padlock.__index = Padlock



function Padlock.new(protectionObj, blurbObj, cfg)

	local obj = BannerTemplate.new(protectionObj, cfg) -- This doesn't need the blurb.

	obj.imageWidth = 20

	obj.imageCaption = blurbObj:makeBannerText('tooltip')

	obj._imageAlt = blurbObj:makeBannerText('alt')

	obj._imageLink = blurbObj:makeBannerText('link')

	obj._indicatorName = cfg.padlockIndicatorNamesprotectionObj.action

		or cfg.padlockIndicatorNames.default

		or 'pp-default'

	return setmetatable(obj, Padlock)

end



function Padlock:__tostring()

	local frame = mw.getCurrentFrame()

	-- The nowiki tag helps prevent whitespace at the top of articles.

	return frame:extensionTag{name = 'nowiki'} .. frame:extensionTag{

		name = 'indicator',

		args = {name = self._indicatorName},

		content = self:renderImage()

	}

end



--------------------------------------------------------------------------------

-- Exports

--------------------------------------------------------------------------------



local p = {}



function p._exportClasses()

	-- This is used for testing purposes.

	return {

		Protection = Protection,

		Blurb = Blurb,

		BannerTemplate = BannerTemplate,

		Banner = Banner,

		Padlock = Padlock,

	}

end



function p._main(args, cfg, title)

	args = args or {}

	cfg = cfg or require(CONFIG_MODULE)



	local protectionObj = Protection.new(args, cfg, title)



	local ret = {}



	-- If a page's edit protection is equally or more restrictive than its

	-- protection from some other action, then don't bother displaying anything

	-- for the other action (except categories).

	if not yesno(args.catonly) and (protectionObj.action == 'edit' or

		args.demolevel or

		not getReachableNodes(

			cfg.hierarchy,

			protectionObj.level

		)[effectiveProtectionLevel('edit', protectionObj.title)])

	then

		-- Initialise the blurb object

		local blurbObj = Blurb.new(protectionObj, args, cfg)

	

		-- Render the banner

		if protectionObj:shouldShowLock() then

			ret#ret + 1 = tostring(

				(yesno(args.small) and Padlock or Banner)

				.new(protectionObj, blurbObj, cfg)

			)

		end

	end



	-- Render the categories

	if yesno(args.category) ~= false then

		ret#ret + 1 = protectionObj:makeCategoryLinks()

	end

	

	return table.concat(ret)	

end



function p.main(frame, cfg)

	cfg = cfg or require(CONFIG_MODULE)



	-- Find default args, if any.

	local parent = frame.getParent and frame:getParent()

	local defaultArgs = parent and cfg.wrappersparent:getTitle():gsub('/sandbox$', '')]



	-- Find user args, and use the parent frame if we are being called from a

	-- wrapper template.

	getArgs = getArgs or require('Module:Arguments').getArgs

	local userArgs = getArgs(frame, {

		parentOnly = defaultArgs,

		frameOnly = not defaultArgs

	})



	-- Build the args table. User-specified args overwrite default args.

	local args = {}

	for k, v in pairs(defaultArgs or {}) do

		argsk = v

	end

	for k, v in pairs(userArgs) do

		argsk = v

	end

	return p._main(args, cfg)

end



return p