Module:Protection banner: Difference between revisions

From NvWiki
Jump to navigation Jump to search
m 1 revision imported: Import modules used with Template:Infobox software
Line 1: Line 1:
{{Permanently protected}}
-- This module implements {{pp-meta}} and its daughter templates such as
{{talk header}}
-- {{pp-dispute}}, {{pp-vandalism}} and {{pp-sock}}.


== Miscategorisation at Wikipedia fully protected templates ==
-- 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')


Extended-confirmed templates are currently erroneously being categorised into [[:Category:Wikipedia fully protected templates]]. That category has a note "For technical reasons, this category also erroneously contains extended confirmed protected templates.", but neither that comment or its edit summary indicates what this "technical explanation" is. I presume this module is responsible for populating that category? Can this reason be explained? (ping {{ping|Mr. Stradivarius|p=}}, in case you know) [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 16:52, 28 August 2020 (UTC)
-- Lazily initialise modules and objects we don't always need.
:{{ping|ProcrastinatingReader}} It sounds like the module needs to be fixed, but I'm not sure of the exact fix needed yet. Do you have an example of a page that is wrongly categorised? — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr.&nbsp;Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪&nbsp;talk&nbsp;♪]]</sup> 00:56, 29 August 2020 (UTC)
local getArgs, makeMessageBox, lang
:And yes, the module is responsible for populating the category. In particular, the category list is in [[Module:Protection banner/config]], about two-thirds of the way down. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr.&nbsp;Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪&nbsp;talk&nbsp;♪]]</sup> 00:57, 29 August 2020 (UTC)
::{{u|Mr. Stradivarius}}, examples: [[Template:Justice League characters]], [[Template:Palestinian militancy attacks in the 1960s]], [[Template:Gs/topics]] [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 01:29, 29 August 2020 (UTC)
::Very quick glance at 30 secs of reading the comments in the config, but perhaps the issue is just that we don't have a [[:Category:Wikipedia extended-confirmed protected templates]] and thus it's resolving up the ladder to the fully protected ones? A complete and utter guess, though, probably wrong... [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 01:32, 29 August 2020 (UTC)
:::Hmm, looks like Mdaniels was working on something similar a few months ago. I've made a slight adjustment to the config sandbox, perhaps this will work? Not quite sure how to test, though. I tried changing the version at [[Template:Palestinian militancy attacks in the 1960s]] to the sandbox v, but it isn't overriding (I'm guessing the template namespace does some kind of autodetect to insert the lock, hence overrides my manual one). And on that note, just wondering, why does {{t|Pp-extended}} exist rather than just using {{t|Pp}} (which is used for all other protections & at least in source appears to do the same thing as the former?) [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 01:47, 29 August 2020 (UTC)
::::{{ping|ProcrastinatingReader}} It looks like your edit did the trick, and you are right, it is just a matter of editing that table so that extended-confirmed-protected templates don't fall through to the default case. The best way to test would be to add some new tests to [[Module:Protection banner/config/testcases]]. Unfortunately, previous editors have edited the config without updating the test cases, so we will need to fix the existing test cases as well. For live testing, I recommend [[User:Jackmcbarn/advancedtemplatesandbox.js]], which you can use to test the sandbox out on live pages without updating the main module. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr.&nbsp;Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪&nbsp;talk&nbsp;♪]]</sup> 01:57, 29 August 2020 (UTC)
:::::That script is handy, I think C&C recommended it to me a little while ago actually but I forgot about it. Synced the update and it seems to be populating the category (awfully slowly, not too sure how the software decides when to update the categorisation). Also fixed most of the testcases. Very, very well documented modules by the way. Most paid code I come across has far worse documentation. I'm actually somewhat surprised you're not professionally a developer. [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 17:16, 29 August 2020 (UTC)
::::::{{ping|ProcrastinatingReader}} Thank you for updating the module, and for fixing the test cases! It is much appreciated. I think you do need the second hyphen in the category name, though: "Wikipedia extended-confirmed protected templates" with one hyphen translates to "protected templates that are extended-confirmed" (whatever that may mean); "Wikipedia extended-confirmed-protected templates" with two hyphens translates to "templates which have extended-confirmed protection", which I think is the one we want here. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr.&nbsp;Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪&nbsp;talk&nbsp;♪]]</sup> 14:08, 30 August 2020 (UTC)
:::::::{{u|Mr. Stradivarius}}, that change is fine with me. By the way, I was looking to find a fully protected page and came across [[:Category:Wikipedia fully protected pages]]. Every article directly in this category isn't fully protected, which confused me a bit. Then I realised [https://en.wikipedia.org/w/index.php?title=Alissa&redirect=no they're redirects]. We tend to put the redirects into [[:Category:Fully protected redirects]], which has 1,375 members. Any idea why some are also/instead being categorised into the former category, which only has 553 members? I think it may be because they're also move protected (the few examples I checked are also in [[:Category:Wikipedia indefinitely move-protected pages]]), but if that's the case I guess we need an extra config case to catch these so they're not falling into the generic category. If my guess is correct, do we want to just change the config case to |all (rather than |edit), or do we want to create a separate category for fully-protected moves?{{pb}}My side question is, why is the module adding them to multiple categories? I know that's good behaviour, but what's the code logic behind it? Is it looking for a category for each type of protection separately (as in: edit/move are put through the hierarchy for a category separately)? [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 14:18, 30 August 2020 (UTC)
:::::::After some further digging, this looks like a mess (but not on the part of this module). Looks like this module isn't responsible for the fully protected redirects category, that's done manually and the category added by redirect templates like {{t|R fully protected}}. So we have [https://en.wikipedia.org/w/index.php?title=Armando_(Blogger)&action=edit some redirects] which aren't tagged with a lock and only in the latter cat, and other articles which are only tagged with the lock, and thus only in the fully-protected category. The ones tagged with both the template and the lock being placed in both. This kinda makes the tracking categories an inconsistent mess and a bit useless as a result, really. Not sure what the best way to clean this up is? I know we can check for redirect using Lua, so we can always add that in, but not sure what the performance impact would be? We'd probably need a bot to fix pages like the one I linked, too? [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 14:30, 30 August 2020 (UTC)
::::::::{{ping|ProcrastinatingReader}} I've made the change to add the extra hyphen, so the pages should start to slowly filter over to the new category. To answer your earlier question, the updating is done by the [[WP:JQ|job queue]], which can be very slow for category changes caused by a template edit. I've seen it take months before. As for the multiple protection categories, this module only adds one protection category per #invoke, but there can be multiple protection templates on a page, and as you found, there are other templates which add protection categories as well. To fix this in an elegant way, we would probably need to make a separate module for generating protection categories which can be called from all of the different templates involved. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr.&nbsp;Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪&nbsp;talk&nbsp;♪]]</sup> 15:04, 30 August 2020 (UTC)
:::::::::{{u|Mr. Stradivarius}}, hmm, in the case of [https://en.wikipedia.org/w/index.php?title=Alissa&redirect=no this] what's adding the lock (in read mode)? And why is that thing not adding the lock [https://en.wikipedia.org/w/index.php?title=Armando_(Blogger)&action=edit here]? These look quite similar to me? [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 15:07, 30 August 2020 (UTC)
::::::::::{{ping|ProcrastinatingReader}} That's [[Template:Rcat shell]], which is calling [[Template:pp-protected]] based on the edit protection level. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr.&nbsp;Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪&nbsp;talk&nbsp;♪]]</sup> 15:14, 30 August 2020 (UTC)
:::::::::::{{u|Mr. Stradivarius}}, I see. Isn't the easiest way to clean this up just to remove {{tlx|pp-protected|small{{=}}yes}} from {{t|Rcat shell}}? Then it would only call {{t|R fully protected}}, which adds the proper category. The only issue would be the lack of lock, then, but we can fix that by adding, to {{t|R fully protected}}, something like: {{tlx|pp|small{{=}}yes|category{{=}}no}}. [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 15:35, 30 August 2020 (UTC)
::::::::::::No, don't remove it. That would undo the work carried out by myself and others (such as {{u|Paine Ellsworth}}) over the last few years to try and eliminate the use of dedicated templates that only apply to a specific prot level. So when a page has {{tlx|rcat shell}} it should not also have any other protection template, not even {{tlx|r protected}} or similar. The thing about {{tlx|pp-protected}} is that it autodetects the prot level and doesn't need adjusting when a page is raised from semi-prot to full-prot, etc. If you find a page (redirect or otherwise) in the "wrong" protection category, first apply a [[WP:NULLEDIT]] and see if that resolves it. --[[User:Redrose64|<span style="color:#a80000; background:#ffeeee; text-decoration:inherit">Red</span>rose64]] &#x1f339; ([[User talk:Redrose64|talk]]) 09:28, 31 August 2020 (UTC)
:::::::::::::{{u|Redrose64}}, these are in the wrong categories due to an inherent bug in the templating. Null edits won't resolve the issue (also, I can't null edit a fully protected page). Please see discussion above. The change I suggested shouldn't defy your efforts. {{t|r protected}} accounts for other protection levels as well, and so moving {{t|pp}} into there for this case wouldn't cause any negative effects as far as I can see. If you're seeing something I'm not, please elaborate. To be clear, I'm only proposing moving it for the fully protected case (which necessarily calls {{t|r protected}}, so this doesn't cause any omissions). There's absolutely no scenarios I can see where this goes wrong? See sandboxes for example. [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 20:17, 31 August 2020 (UTC)
::::::::::::::Well {{u|ProcrastinatingReader|PR}}, seems you're coming in on an old problem we've been noticing and trying to fix for several years now. On this talk page alone it goes back at least to 2016 and when {{tl|Redirect category shell}} was the {{tl|This is a redirect}} template. This challenge applies not just to Extended-confirmed-protected but also to others like the {{tl|-r}} redirect, which is template-protected. There appear to be so many layers of this issue that some seriously good admins and template editors, as well as Lua experts have tried to fix it and then apparently put it off to tackle more serious challenges that come up. It's a really tough nut to crack, so I wouldn't get my hopes up for a solution any time soon. However, I myself do hope that it will eventually be well-resolved. '''''[[User:Paine Ellsworth|<span style="font-size:92%;color:darkblue;font-family:Segoe Script">P.I.&nbsp;Ellsworth</span>]]'''''&nbsp;&nbsp;[[Editor|<span style="color:black">ed.</span>]]&nbsp;[[User talk:Paine Ellsworth|<sup>put'r&nbsp;there</sup>]]&nbsp;<small>12:01, 1 September 2020 (UTC)</small>
{{od|::::::::::::::}}
{{u|Paine Ellsworth}}, do you see any flaws with my proposed solution? Obviously it would only affect the niche case of fully-protected redirects using one of the two templates (not the other ones, which would remain as broken as they are now), but since I cannot see it causing any issues for that case I think my solution it's better than nothing, and at least it would make the fully-protected category less useless for other purposes. Noting that the shell necessarily calls {{t|r protected}} makes this an easy case to solve, I think. [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 12:08, 1 September 2020 (UTC)
:You appear to be looking to remove the {{tl|pp-protected}} template from the shell, thus changing:
<pre>
{{#switch: {{PROTECTIONLEVEL:edit}}
  |sysop={{pp-protected|small=yes}}{{R protected|embed=yes}}
  |templateeditor={{pp-protected|small=yes}}{{R template protected|embed=yes}}
  |extendedconfirmed={{pp-protected|small=yes}}{{R extended-protected|embed=yes}}
  |autoconfirmed={{pp-protected|small=yes}}{{R semi-protected|embed=yes}}
</pre>
:to:
<pre>
{{#switch: {{PROTECTIONLEVEL:edit}}
  |sysop={{R protected|embed=yes}}
  |templateeditor={{pp-protected|small=yes}}{{R template protected|embed=yes}}
  |extendedconfirmed={{pp-protected|small=yes}}{{R extended-protected|embed=yes}}
  |autoconfirmed={{pp-protected|small=yes}}{{R semi-protected|embed=yes}}
</pre>
:is that correct? And then further, to get back the lock icon that would be lost, you propose to insert the {{tl|pp-protected}} template into the {{tl|R fully protected}} rcat template? Where exactly would you insert it? '''''[[User:Paine Ellsworth|<span style="font-size:92%;color:darkblue;font-family:Segoe Script">P.I.&nbsp;Ellsworth</span>]]'''''&nbsp;&nbsp;[[Editor|<span style="color:black">ed.</span>]]&nbsp;[[User talk:Paine Ellsworth|<sup>put'r&nbsp;there</sup>]]&nbsp;<small>12:38, 1 September 2020 (UTC)</small>
::{{u|Paine Ellsworth}} that's correct. For your question, see [[Template:R fully protected/sandbox]]. Both sandboxes are updated with the change I'm proposing.{{pb}}(the second option is to keep both templates as-is, but just add a |category=no to the shell. This works the same to fix our categorisation issue, but since currently half the fully-protected redirects don't use the shell, they don't have the lock. I think the above approach is better so we have the lock in all cases, as well as fixing categorisation). [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 19:43, 1 September 2020 (UTC)
:::Like you, {{u|ProcrastinatingReader|PR}}, I'm unable to test your changes on fully-protected redirects; however, I can test the changes on template-protected redirects and found that they are removed from {{cat|Wikipedia template-protected templates}} when the {{tl|Redirect category/sandbox}} is used. This leads me to think that your changes will accomplish what you want them to do. One problem is that your changes did not remove the template-protected redirect from {{cat|Wikipedia fully protected pages}}, and that is one of our main issues. So your solution might very well be a good partial fix (that would need testing by an admin to confirm); however, it does not address other important issues I have with incorrect and improper categorization as I've raised elsewhere on this talk page. '''''[[User:Paine Ellsworth|<span style="font-size:92%;color:darkblue;font-family:Segoe Script">P.I.&nbsp;Ellsworth</span>]]'''''&nbsp;&nbsp;[[Editor|<span style="color:black">ed.</span>]]&nbsp;[[User talk:Paine Ellsworth|<sup>put'r&nbsp;there</sup>]]&nbsp;<small>10:18, 2 September 2020 (UTC)</small>
::::Thanks for testing, {{u|Paine Ellsworth}}. That problem you note unfortunately won't be fixed by these changes, as this modification in the same way applied to TE redirects won't work in the same way. That would require a new rule in the module and some modifications to the templates, I believe, but I haven't looked into the case of TE redirects closely, thus only propose a change for the full-protected redirect case. May look into the other cases later on. The issues in others should also become more apparent once the fully-protected cat isn't polluted with nonsense.{{pb}}I've tested my changes on [[Alissa]] (a fully-protected redirect) using the templatesandbox script suggested above, and it does correctly categorise this redirect. If any admins wish to test further, on the live redirect by changing it to the sandbox, that'd be great. Otherwise, I think there's sufficient evidence to make this relatively straight-forward change in the coming days, if no objections to the technical details are made. [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 11:38, 2 September 2020 (UTC)
:::::You're welcome, {{u|ProcrastinatingReader}}''!'' Yes, it would be very helpful if the admins who've been working on this would test the FP redirects with the sandboxed code. Another question I have would be what do you think would happen if, instead of placing <code><nowiki>{{pp|small=yes|category=no}}</nowiki></code> in the {{tl|R fully protected}} rcat, what would be the difference if we just added the {{para|category|no}} parameter to the existing {{tl|pp-protected}} template (which of course redirects to template {{tl|pp}}) in the {{tl|Rcat shell}}? Would that accomplish the same thing? or do you think there would be different results? '''''[[User:Paine Ellsworth|<span style="font-size:92%;color:darkblue;font-family:Segoe Script">P.I.&nbsp;Ellsworth</span>]]'''''&nbsp;&nbsp;[[Editor|<span style="color:black">ed.</span>]]&nbsp;[[User talk:Paine Ellsworth|<sup>put'r&nbsp;there</sup>]]&nbsp;<small>12:14, 2 September 2020 (UTC)</small>
::::::{{u|Paine Ellsworth}}, in terms of categorisation, it would be exactly the same result. However, a lot of fully-protected redirects do not use the shell, and instead only use {{t|R fully protected}}. As a result, they don't have the lock icon in the top right. I think the approach suggested would also correct this 'error' (if you like) by adding the lock in those cases too, and I think the lock should be there, so my initial thoughts were that this instead is a better approach. As you (and others) have dealt with this issue over a longer period of time than I, I'd be interested to hear your thoughts on this matter. [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 12:33, 2 September 2020 (UTC)
:::::::I would support adding the pp template to the fully-protected rcat as an interim solution, and I would also support adding the {{para|category|no}} parameter to the shell for all the protection templates in the shell. May as well add the pp template to {{tl|R template-protected}}, {{tl|R extended-protected}} and {{tl|R semi-protected}} as well, because when those are used without the shell, they too do not add a lock to the top of the redirect page.
:::::::A better solution in the long run might be for a bot to find all of the protected redirects and replace the protection rcats with the rcat shell; however, some editors might complain if other appropriate rcats are not also added to justify the use of the shell. And that could not be facilitated by a bot. That would have to be done manually, which would take an army of Wikignomes to accomplish. < sigh > '''''[[User:Paine Ellsworth|<span style="font-size:92%;color:darkblue;font-family:Segoe Script">P.I.&nbsp;Ellsworth</span>]]'''''&nbsp;&nbsp;[[Editor|<span style="color:black">ed.</span>]]&nbsp;[[User talk:Paine Ellsworth|<sup>put'r&nbsp;there</sup>]]&nbsp;<small>13:29, 2 September 2020 (UTC)</small>
::::::::I will look into those templates too, when I have a chance, to confirm there would also be no adverse affects of doing so. I agree we need a better solution to really address all components of this issue. I raised the idea of a bot above. Issue is that neither of us could operate the bot (it would have to be adminbot to edit fully-protected redirects). Not sure of many active admins who have experience & interest in this area, so unless you would like to put in an RfA and operate it I think the bot point is moot. WikiGnomes (even if logistically possible) wouldn't help for same reason, fully-protected edit requests take forever to process (and I've only seen 1 or 2 admins who actively attend to them). Unlikely 1000 of them would be done. Even if they could be, it's double the amount of time taken per redirect (time of proposer + time of admin reviewer). [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 15:01, 2 September 2020 (UTC)


====Move-protected====
-- Set constants.
{{u|Mr. Stradivarius}}, thoughts on what to do about move-protected pages to a non-sysop level? Currently, they just default up the chain to [[:Category:Wikipedia fully protected pages]]. I saw a testcase for this that was failing, so I made [[Special:Diff/976938143]], but really this seems wrong. Doesn't exactly make sense to categorise TE move-protected into TE. We do have categories for sysop move protection, eg <code>['all|template|all|sysop|move'] = 'Wikipedia move-protected templates'</code>, but none really for lesser move protection, and it kinda seems somewhat silly to have "Wikipedia template-editor move-protected templates"? Hence, not sure what solution would be best here. [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 19:11, 6 September 2020 (UTC)
local CONFIG_MODULE = 'Module:Protection banner/config'
:Why not introduce a separate category tree for move-protection? From [[Wikipedia:Move protection]] it is unclear, how many different levels of move protection there is except {{tq|fully move-protected pages}}. Other relevant bits:
:* {{tq|Fully edit-protected pages are also implicitly move-protected.}}
:* {{tq|All files are implicitly move-protected; only file movers and administrators can move files.}}
: —⁠[[User:Andrybak|andrybak]] ([[User talk:Andrybak|talk]]) 21:38, 6 September 2020 (UTC)
:*I think in theory you can move protect to all levels, but for the most part non-sysop move-protection not matching edit protection is quite a rare scenario. It happens on some mainspace pages (usually to ECP move), there's a couple templates that have this (edit=semi, move=TE). I think many of them are IAR actions, rather than codified in guidelines, but reasonable protections nevertheless. A move category tree is one idea, yeah, and not awfully difficult to implement, at least in the module. There's a bunch of scattered templates that would need to be double-checked, as well. It would be worth noting that if we did that, stuff like [[:Category:Wikipedia move-protected templates]] should probably be a cat containing child cats only, so we'd have to make sure no bots are going to be troubled by that. [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 22:02, 6 September 2020 (UTC)
:{{ec}} {{to|ProcrastinatingReader}} I'd also like to hear thoughts from [[User:Redrose64|<span style="color:#a80000; background:#ffeeee; text-decoration:inherit">Red</span>rose64]], [[User:MSGJ|Martin]], [[User:Anomie|Anomie⚔]], [[User:Wbm1058|wbm1058]], [[User:Xaosflux|<span style="color:#FF9933; font-weight:bold; font-family:monotype;">xaosflux</span>]] and [[User:Jackmcbarn|Jackmcbarn]] that would be helpful on this issue. As a "tough nut to crack", we need all the help we can get. '''''[[User:Paine Ellsworth|<span style="font-size:92%;color:darkblue;font-family:Segoe Script">P.I.&nbsp;Ellsworth</span>]]'''''&nbsp;&nbsp;[[Editor|<span style="color:black">ed.</span>]]&nbsp;[[User talk:Paine Ellsworth|<sup>put'r&nbsp;there</sup>]]&nbsp;<small>21:48, 6 September 2020 (UTC)</small>
::{{replyto|Andrybak}} Move protection has exactly the same five levels as edit-, upload- and create-protection, i.e. none, semi, EC, template and full. However, since IPs and unconfirmed editors cannot move pages, a page with no move protection is effectively semi-protected for moves. It is rare to have a page that is explicitly semi-prot for moves but has no edit protection; these are usually mistakes and, when discovered, are often reduced to having no move protection for the sake of tidiness. --[[User:Redrose64|<span style="color:#a80000; background:#ffeeee; text-decoration:inherit">Red</span>rose64]] &#x1f339; ([[User talk:Redrose64|talk]]) 11:10, 7 September 2020 (UTC)


====Userspace scripts====
--------------------------------------------------------------------------------
We've got a lot of cat pollution where someone adds a userspace script and chucks a <code><nowiki>// {{pp-template}}</nowiki></code> or something into their css/js files, causing a gold lock and sysop-protected categorisation (eg [[User:GoldenRing/common.js]], [[User:BeywheelzLetItRip/common.css]], [[User:AHollender (WMF)/sandbox/2019–20 coronavirus pandemic data/2019–20 coronavirus pandemic data/styles.css]], etc). Rather than have IAdmins constantly removing this stuff, perhaps it's a good idea for this module should just not show lock or categorise (basically just skip) any .js/.css files in userspace (which are all IAdmin protected, globally)? [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 22:15, 6 September 2020 (UTC)
-- Helper functions
:Done [[Module:Protection banner/sandbox|in sandbox]] if anyone wants to take a look. [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 22:38, 6 September 2020 (UTC)
--------------------------------------------------------------------------------
::I took a look at the code. It looks basically good - thanks for contributing it. :) There are a couple of things I would change:
::* Instead of having the code in <code>Protected:isProtected()</code>, I would put it directly in <code>Protection:makeCategoryLinks()</code>, or maybe better, put it in a dedicated <code>Protection:shouldBeCategorized()</code> method and call that from makeCategoryLinks. Saying user .js or .css pages aren't protected when they effectively are protected is a bit confusing, and I think that moving the code would make the intention more explicit, and maybe avoid future bugs.
::* It could do with some test cases at [[Module:Protection banner/testcases]].
::Best — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr.&nbsp;Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪&nbsp;talk&nbsp;♪]]</sup> 15:32, 7 September 2020 (UTC)
:::{{u|Mr. Stradivarius}}, thanks for reviewing! Re bullet 1: I popped it into <code>:isProtected()</code> to also stop it showing the lock, as well as stopping the categorisation, since my thought was that showing a gold sysop lock on these types of files seems slightly misleading & undesirable. Also seemed like a good idea to pop them into the "wrong categories" cat to have their // {{tlf|pp-template}} removed seemed like a good idea. Easiest way to achieve all points was to edit :isProtected(). Of course, open to thoughts on if any of these fixes are themselves undesirable. [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 16:11, 7 September 2020 (UTC)
::::{{ping|ProcrastinatingReader}} I see - if we need to suppress the padlock icons as well, then I agree that <code>:isProtected()</code> is a very convenient place to add the code. I think the problem is just one of naming, then, which I [[Special:Diff/977374710|had a go at tackling]] in the sandbox. I couldn't think of a good name for a method that means "the template should output a protection category, and a banner/padlock icon", though, so I cheated by making an alias. There's probably a better way of doing it. Also, I fixed the test cases, which appeared to have been broken for quite some time. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr.&nbsp;Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪&nbsp;talk&nbsp;♪]]</sup> 14:24, 8 September 2020 (UTC)
:::::{{u|Mr. Stradivarius}}, looks good to me! [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 19:27, 9 September 2020 (UTC)
:::::{{u|Mr. Stradivarius}}, is this good to merge into live? [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 19:08, 15 September 2020 (UTC)
::::::{{ping|ProcrastinatingReader}} Not yet - there are some test cases missing, and I noticed an edge case that the user JS/CSS code needs to cover (i.e. user pages only have the JS/CSS content type if they are a subpage; <code>User:Foo.css</code> would be a wikitext page). — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr.&nbsp;Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪&nbsp;talk&nbsp;♪]]</sup> 00:20, 16 September 2020 (UTC)
:::::::Interesting. Technically those pages could be CSS too, if manually changed. The best way to check all would be to use contentModel I guess, but it’s a “maybe expensive function”; a check on the page title will probably handle 99.9% of cases. [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 09:54, 16 September 2020 (UTC)
::::::::Using the content model shouldn't be a problem, as we would only be making one expensive function call. And I'm guessing the result would be cached per page, so there would probably still be only one expensive function call even if we have multiple #invokes on a page. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr.&nbsp;Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪&nbsp;talk&nbsp;♪]]</sup> 04:19, 19 September 2020 (UTC)
::::::::Also, checking the [[mw:Extension:Scribunto/Lua reference manual#Expensive properties|docs]], it looks like fetching the content model for the current page doesn't count towards the expensive function count, so it shouldn't actually have any effect. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr.&nbsp;Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪&nbsp;talk&nbsp;♪]]</sup> 07:18, 19 September 2020 (UTC)
:::::::::I've just put the code up live - let me know if you notice any issues. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr.&nbsp;Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪&nbsp;talk&nbsp;♪]]</sup> 13:07, 19 September 2020 (UTC)
::::::::::{{u|Mr. Stradivarius}}, looks good right now. Am curious about one thing. I know categories don't update with contents immediately, but I thought a [[Wikipedia:Purge#forcerecursivelinkupdate|purge with forcerecursivelinkupdate]] on the page immediately updated that? Tried on a couple but they don't disappear from [[:Category:Wikipedia fully protected pages]] for some reason - any idea? [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 18:33, 19 September 2020 (UTC)
:::::::::::It's not worked for months, if not longer: see [[Wikipedia:Village pump (technical)/Archive 181#Sending a POST to the API to purge]]. --[[User:Redrose64|<span style="color:#a80000; background:#ffeeee; text-decoration:inherit">Red</span>rose64]] &#x1f339; ([[User talk:Redrose64|talk]]) 21:45, 19 September 2020 (UTC)


====Redirect sub-categorisation====
local function makeCategoryLink(cat, sort)
[[:Category:Fully protected redirects]] is a little useless for maintenance purposes, because it has 1,500 members with no subcategories. Ideally this should be split up, e.g. redirects of alternate name of a BLP, redirects protected due to vandalism, project redirects pre-emptively protected, etc. Discussed briefly above in [[Module_talk:Protection_banner#Categorization_of_protected_redirects]], {{u|Mr. Stradivarius}} gave 3 options which I also think are the best few. I think it can easily be done with this module. We could create a bunch of wrappers and get around the named caveat by adding a title check for isRedirect if the wrapper is for a redirect (probably easiest option). These would use the reason key. It would also keep per-namespace options open, but we wouldn't be able to use normal banner data (could get around this by having a -redirect suffix in reason for cat, but fetching data from parent for lock). There's also option 3 of making a redirect key in the algorithm, but that's maybe a bit of work (Mr. S or Jack might know best). [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 14:53, 7 September 2020 (UTC)
if cat then
return string.format(
'[[%s:%s|%s]]',
mw.site.namespaces[14].name,
cat,
sort
)
end
end


'''Update'''
-- 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


Making a bit of a laundry list of protection category 'issues', feel free to add/change. I guess we can figure out solutions from there. {{u|Paine Ellsworth}} may be interested? Re. redirects, I know {{u|Mr. Stradivarius}} suggested [[Module_talk:Protection_banner#Categorization_of_protected_redirects|above]] the ideas of {{tq|Add redirect detection as a full-blown key to the protection category algorithm}}, or a new module to deal with redirects. I also had [[Special:Diff/977097115/977183814|an idea]] of suffixing -redirect to the reason. I think the adding to algorithm approach is best, the issue overlaps into non-redirects and I think it's easier than having a near-duplicate module. However, I think it may also just be better to cut our losses, focus on fixing & having categorisations based mainly on protection level and duration (indef/temp), and suggesting Petscan if one wishes to get useful stuff out of them. For example, to figure out extended confirmed protections NOT DS one can use the "has none templates", on talk page, of "ArbCom Arab-Israeli enforcement" + [...]. [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 17:20, 26 September 2020 (UTC)
local function makeFullUrl(page, query, display)
return string.format(
'[%s %s]',
tostring(mw.uri.fullUrl(page, query)),
display
)
end


In both cases, here's some issues:
-- Given a directed graph formatted as node -> table of direct successors,
* [[:Category:Wikipedia move-protected pages]] needs to be able to handle non-admin move protections (currently they just go into the fully-prot cat). But since each run of the module only categorises once, it would require completely changing the category tree structure I think (like the standard protections) to make this work smoothly? Or, we can just mash everything up into one move-protected category per protection level (ditch the namespace stuff) and suggest Petscan for namespace categorisation (likely the easiest option?)
-- get a table of all nodes reachable from a given node (though always
* [[:Category:Wikipedia protected biographies of living people]] is ineffective, and hence it (and its subcategories) nearly empty. Admins rarely use {{t|pp-blp}}, Twinkle just adds {{t|pp-30-500}}, e.g. [[Sushant Singh Rajput]], with a protection reason usually including "Violations of the biographies of living persons policy" (but not always). A few things a bot can do here, but I note that Petscan can also be used for the same purpose, so do we just want to scrap {{t|pp-blp}} and only categorise by protection level instead? It seems to at least be used for semi-protections, but I wonder how many semis are just mashed up in other cats, like [[:Category:Wikipedia semi-protected pages]] (a lot, at a glance). Similar issue with [[:Category:Wikipedia extended-confirmed-protected pages]].
-- 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
toWalk[k] = nil
retval[k] = true
for _,v in ipairs(graph[k]) do
if not retval[v] then
toWalk[v] = true
end
end
end
end


::{{to|ProcrastinatingReader}} yes, I'm very interested and watching all this closely. Much of it seems above my pay grade and ken, but I read and watch anyway hoping this will be resolved by those who know more than I do about these things. '''''[[User:Paine Ellsworth|<span style="font-size:92%;color:darkblue;font-family:Segoe Script">P.I.&nbsp;Ellsworth</span>]]'''''&nbsp;&nbsp;[[Editor|<span style="color:black">ed.</span>]]&nbsp;[[User talk:Paine Ellsworth|<sup>put'r&nbsp;there</sup>]]&nbsp;<small>15:57, 27 September 2020 (UTC)</small>
--------------------------------------------------------------------------------
-- Protection class
--------------------------------------------------------------------------------


== Catonly ==
local Protection = {}
Protection.__index = Protection


{{re|Mr. Stradivarius}} I made a change to sandbox that adds a parameter called "catonly". If it's "yes", then the banner/padlock will be hidden and only the category will be provided. Added 2 testcases which pass. This would implement [https://en.wikipedia.org/wiki/Wikipedia:Templates_for_discussion/Log/2020_October_25#Template:Pp-move this TfD]. Is that good to go? [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 19:47, 15 July 2021 (UTC)
Protection.supportedActions = {
:{{ping|ProcrastinatingReader}} Thank you for implementing this! I really appreciate it when someone takes the time to write test cases. :) Looks like you forgot to sync the latest changes from the main module (see the [https://en.wikipedia.org/wiki/Special:ComparePages?page1=Module%3AProtection+banner&rev1=1013845490&page2=Module%3AProtection+banner%2Fsandbox&rev2=1033774702&action=&diffonly=&unhide= diff]), so you should resync and check that your changes still pass the unit tests. We could probably do with another test case to show that a padlock icon is shown when {{para|catonly|no}}. And maybe a similar one for categories if you want to be thorough. Also, I see that you're duplicating the UNIQ...QINU pattern to detect strip markers - that should probably be made its own variable, in case it needs to be changed in the future. The sandbox code looks fine, although as that if/then condition gets more complex, I would be tempted to split it out into its own function for readability. Let me know if you want me to clarify anything, and once you've fixed the above issues feel free to deploy the code if all the unit tests pass. Best — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr.&nbsp;Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪&nbsp;talk&nbsp;♪]]</sup> 11:45, 16 July 2021 (UTC)
edit = true,
::Thanks for reviewing! Added those tests and added a test for banners as well. Also split that pattern into a variable. All tests pass, so I've deployed the code. [[User:ProcrastinatingReader|ProcrastinatingReader]] ([[User talk:ProcrastinatingReader|talk]]) 12:01, 16 July 2021 (UTC)
move = true,
autoreview = true,
upload = true
}


== Template-protected edit request on 3 February 2022 ==
Protection.bannerConfigFields = {
'text',
'explanation',
'tooltip',
'alt',
'link',
'image'
}


{{edit template-protected|Module:Protection banner/config|answered=yes}}
function Protection.new(args, cfg, title)
Please add the following to [[Module:Protection banner/config]] near line 772:
local obj = {}
<pre>
obj._cfg = cfg
['all|module|all|extendedconfirmed|edit']      = 'Wikipedia extended-confirmed-protected modules',
obj.title = title or mw.title.getCurrentTitle()
</pre>
[https://en.wikipedia.org/wiki/Special:ProtectedPages?namespace=828&type=edit&level=extendedconfirmed&size-mode=min&size= ECP modules] currently categorize into [[:Category:Wikipedia fully-protected modules]], this would move them to [[:Category:Wikipedia extended-confirmed-protected modules]]. [[User:AntiCompositeNumber|AntiCompositeNumber]] ([[User talk:AntiCompositeNumber|talk]]) 23:27, 3 February 2022 (UTC)
: {{Done}}: [[Special:Diff/1069763876]]. —⁠[[User:Andrybak|andrybak]] ([[User talk:Andrybak|talk]]) 23:50, 3 February 2022 (UTC)
::@[[User:Andrybak|Andrybak]] Thanks! [[User:AntiCompositeNumber|AntiCompositeNumber]] ([[User talk:AntiCompositeNumber|talk]]) 00:02, 4 February 2022 (UTC)


== Issues with pending changes alongside generic protection ==
-- Set action
if not args.action then
obj.action = 'edit'
elseif Protection.supportedActions[args.action] then
obj.action = args.action
else
error(string.format(
'invalid action: %s',
tostring(args.action)
), 3)
end


At [[Chimpanzee]], the page is both pending changes and semi-protected. {{tl|pp-pc}} does not render, only {{tl|pp-vandalism}} does. Is this expected? <span class="nowrap">&#8212;'''[[User:CX Zoom|CX Zoom]]'''[he/him]</span> <sup class="nowrap">([[User talk:CX Zoom|let's talk]] • {[[Special:Contributions/CX Zoom|C]]•[[User:CX Zoom/X|X]]})</sup> 09:51, 5 August 2022 (UTC)
-- Set level
:{{replyto|CX Zoom}} Yes, only one kind of protection is reported - the "stronger" one. In this case, the {{tlx|pp-vandalism}} picks up the [[WP:SEMI|semi-protection]] for editing which prevents all edits by unconfirmed users; whilst the {{tlx|pp-pc}} picks up the [[WP:PC1|pending changes]] prot, also for editing, which ''allows'' edits by unconfirmed users but doesn't make them visible unless accepted. So in this case the semi-prot is the stronger one. --[[User:Redrose64|<span style="color:#a80000; background:#ffeeee; text-decoration:inherit">Red</span>rose64]] &#x1f339; ([[User talk:Redrose64|talk]]) 20:46, 5 August 2022 (UTC)
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


== Using plain English for protection levels ==
-- 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


The recent high-profile case of readers not understanding protection icons at [[Recession]] led me to review the tooltip text, and it seems that it leaves something to be desired. The messages are currently in forms like {{tq|This article is semi-protected until April 7, 2022 at 1:47 UTC.}} Readers don't know what "semi-protected" or "extended-protected" means, and very few will click through to the [[WP:protection|protection policy page]] to find out. I suggest that we change the wording to plain English to help make it easier to understand. For semi-protection, I'd suggest {{tq|New users cannot edit this article until April 7, 2022, at 1:47 UTC}} (note also two grammar fixes: the added comma and removed period). For EC, {{tq|Only experienced users can edit this article until...}} Thoughts? <span style="color:#AAA"><small>&#123;{u&#124;</small><span style="border-radius:9em;padding:0 5px;background:#088">[[User:Sdkb|<span style="color:#FFF">'''Sdkb'''</span>]]</span><small>}&#125;</small></span> <sup>[[User talk:Sdkb|'''talk''']]</sup> 18:21, 28 August 2022 (UTC)
-- Set reason
: The idea seems reasonable in theory, but the wordings you proposed don't work as the language used for semi-protection and for extended-confirmed-protection read like synonyms of each other. [[User:Pppery|* Pppery *]] [[User talk:Pppery|<sub style="color:#800000">it has begun...</sub>]] 15:10, 29 August 2022 (UTC)
if args[1] then
::What high-profile case? There doesn't seem to be anything on its talk page. --[[User:Redrose64|<span style="color:#a80000; background:#ffeeee; text-decoration:inherit">Red</span>rose64]] &#x1f339; ([[User talk:Redrose64|talk]]) 09:15, 30 August 2022 (UTC)
obj.reason = mw.ustring.lower(args[1])
:::@[[User:Pppery|Pppery]], do you have a suggested alternative? Users won't be seeing both notices at the same time, and I can't think of an intuitive word for those in the 10-500 edit range, so I'm not sure we can do any better. Perhaps for EC, {{tq|New and intermediately experienced editors cannot edit...}}, but that's a little wordier.
if obj.reason:find('|') then
:::@[[User:Redrose64|Redrose64]], https://slate.com/technology/2022/08/wikipedia-recession-article.html. <span style="color:#AAA"><small>&#123;{u&#124;</small><span style="border-radius:9em;padding:0 5px;background:#088">[[User:Sdkb|<span style="color:#FFF">'''Sdkb'''</span>]]</span><small>}&#125;</small></span> <sup>[[User talk:Sdkb|'''talk''']]</sup> 14:29, 30 August 2022 (UTC)
error('reasons cannot contain the pipe character ("|")', 3)
::::Having mulled this over for a while, I can't come up with any wording clearer than my original proposal. I don't think the synonym concern is a blocker, since the tooltips won't be appearing alongside each other and it's fairly intuitive that there's an intermediate level in between "new user" and "experienced user".
end
::::The config page [[Module:Protection_banner/config#L-922|currently relies on PROTECTIONLEVEL]], so improving it will require some coding; would anyone with Lua skills be interested in taking that on? <span style="color:#AAA"><small>&#123;{u&#124;</small><span style="border-radius:9em;padding:0 5px;background:#088">[[User:Sdkb|<span style="color:#FFF">'''Sdkb'''</span>]]</span><small>}&#125;</small></span> <sup>[[User talk:Sdkb|'''talk''']]</sup> 18:06, 5 October 2022 (UTC)
end
:::::{{ping|Sdkb}} I've decided to give this a quick go. Note that I've entirely focused on the padlocks here - the regular big banner text is unaffected, since it's seen less often and I'm not sure you wanted anything there to be changed. I've tried to make the changes as light as possible (since the multiple class structures in the main module with objects being created all over the place is, at best, hard to learn on the spot), and I've come up with a basic config approach (See [[Module:Protection banner/config/sandbox#L-559|here]]) (Note that the move and upload sub-tables could be excessive, since I've never seen their padlocks appear normally, but I've implemented some wording anyways). I've also removed the tooltip from the template and ecp options in the banners table, since this would break messaging (see the thread just below), and also applied the comma you suggested above into the time formatting, though I'm not sure about removing the period (it's possible, but not elegant, likely being a "does the final message end with 'UTC' " check).
:::::Since showing tests for this module can be quite hard, here are the 4 main protection levels with the messages as of the current sandbox version: [[File:Full-protection-shackle.svg|20px|link=Wikipedia:Protection policy#full|alt=Page protected|Only admins can edit this page until January 1, 2022, at 00:00 UTC.]] [[File:Template-protection-shackle.svg|20px|link=Wikipedia:Protection policy#template|alt=Page template-protected|Only experienced template editors and admins can edit this page until January 1, 2022, at 00:00 UTC.]] [[File:Extended-protection-shackle.svg|20px|link=Wikipedia:Protection policy#extended|alt=Page extended-protected|Only experienced users can edit this page until January 1, 2022, at 00:00 UTC.]] [[File:Semi-protection-shackle.svg|20px|link=Wikipedia:Protection policy#semi|alt=Page semi-protected|New users cannot edit this page until January 1, 2022, at 00:00 UTC.]]
:::::Hope this fits roughly what you were thinking. Any comments or requests, just ask. [[User:Aidan9382|Aidan9382]] <sub>([[User talk:Aidan9382|talk]])</sub> 12:31, 9 December 2022 (UTC)
::::::@[[User:Aidan9382|Aidan9382]], sorry for the delayed reply. That looks excellent! I'd support implementing. <span style="color:#AAA"><small>&#123;{u&#124;</small><span style="border-radius:9em;padding:0 5px;background:#088">[[User:Sdkb|<span style="color:#FFF">'''Sdkb'''</span>]]</span><small>}&#125;</small></span> <sup>[[User talk:Sdkb|'''talk''']]</sup> 06:02, 1 March 2023 (UTC)
:::::::@[[User:Aidan9382|Aidan9382]], just wanted to circle back, where are we at with this? Cheers, <span style="border:3px outset;border-radius:8pt 0;padding:1px 5px;background:linear-gradient(6rad,#86c,#2b9)">[[User:Sdkb|<span style="color:#FFF;text-decoration:inherit;font:1em Lucida Sans">Sdkb</span>]]</span> <sup>[[User talk:Sdkb|'''talk''']]</sup> 01:34, 6 March 2024 (UTC)
::::::::{{ping|Sdkb}} (This was over a year ago? God, swear this wasn't that long ago) I haven't made any changes since the last sandbox edit related to this, but that version should be working since its what I used to generate the padlocks in my 9 december message. I've re-synced the config changes back into the current config sandbox version, and the module changes are already still in the sandbox. Changes for the protection messages are welcome since I'm not the best at writing that kind of stuff. You can see an example padlock message in action at [[User:Aidan9382/SafeEnvironmentTesting]] (you can adjust the demolevel as needed to test each padlock - you can't test multiple at once due to the indicators all being named the same). [[User:Aidan9382|Aidan9382]] <sub>([[User talk:Aidan9382|talk]])</sub> 07:44, 6 March 2024 (UTC)


== Tooltip/title does not match the actual protection level ==
-- 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.banners[obj.action] then
configTables[#configTables + 1] = cfg.banners[obj.action][obj.reason]
end
if cfg.defaultBanners[obj.action] then
configTables[#configTables + 1] = cfg.defaultBanners[obj.action][obj.level]
configTables[#configTables + 1] = cfg.defaultBanners[obj.action].default
end
configTables[#configTables + 1] = cfg.masterBanner
for i, field in ipairs(Protection.bannerConfigFields) do
for j, t in ipairs(configTables) do
if t[field] then
obj.bannerConfig[field] = t[field]
break
end
end
end
end
return setmetatable(obj, Protection)
end


While the symbol displayed by templates like {{tl|pp-extended}}, and the link placed behind the symbol, automatically match the actual protection level, the tooltip/title doesn't.
function Protection:isUserScript()
Specifically, the article [[Russia]] is currently full-protected and displays a golden "F" lock, with a link pointing towards the "#full" section of the protection policy. Its title attribute, however, is "This article is extended-confirmed protected".
-- 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


This has been noticed by [[User:Jishiboka1|Jishiboka1]] at [[Talk:Russia]] and should probably be fixed in the module. [[User:ToBeFree|~ ToBeFree]] ([[User talk:ToBeFree|talk]]) 11:44, 1 October 2022 (UTC)
function Protection:isProtected()
return self.level ~= '*'
end


:Note: After some tampering with the module and some test runs, I've found out what the issue is. The module seems to be relying entirely on the template itself for the banner text it uses which is fetched from [[Module:Protection banner/config]]. You can see an example of this if you try to use {{tl|pp-template}} on a semi-protected page - it'll display the template banner's text from the config, but with the protection level at semi. This works ''most'' of the time, as the tooltip messages use <code>${PROTECTIONLEVEL}</code>, which means the message adapts to the protection level of the page. However, the tooltip for ecp is hard-coded to read <code>This ${PAGETYPE} is extended-confirmed protected</code>, which is where the issue is coming from. A solution to this could be just changing the tooltip message to <code>This ${PAGETYPE} is ${PROTECTIONLEVEL}</code>, so it matches the other options' format. [[User:Aidan9382|Aidan9382]] <sub>([[User talk:Aidan9382|talk]])</sub> 13:09, 1 October 2022 (UTC)
function Protection:shouldShowLock()
-- Whether we should output a banner/padlock
return self:isProtected() and not self:isUserScript()
end


{{edit template-protected|Module:Protection banner/config|answered=yes}}
-- Whether this page needs a protection category.
Apparently forgot to do this. Based off of the discussion above, could the <code>This ${PAGETYPE} is extended-confirmed protected</code> message on line 224 be changed to <code>This ${PAGETYPE} is ${PROTECTIONLEVEL}</code> to avoid the module displaying the wrong message when {{tl|pp-extended}} (or similar) is used? Thanks. [[User:Aidan9382|Aidan9382]] <sub>([[User talk:Aidan9382|talk]])</sub> 18:38, 5 October 2022 (UTC)
Protection.shouldHaveProtectionCategory = Protection.shouldShowLock
:{{edited2}}. '''''[[User:Paine Ellsworth|<span style="font-size:92%;color:darkblue;font-family:Segoe Script">P.I.&nbsp;Ellsworth</span>]]'''''&thinsp;,&nbsp;[[Editor|<span style="color:black">ed.</span>]]&nbsp;[[User talk:Paine Ellsworth|<sup>put'r&nbsp;there</sup>]]&nbsp;<small>04:19, 6 October 2022 (UTC)</small>


== Template configuration feedback ==
function Protection:isTemporary()
return type(self.expiry) == 'number'
end


Some of the strings in [[Module:Protection_banner/config]] could be more consistent with how page protection works on Wikipedia.
function Protection:makeProtectionCategory()
if not self:shouldHaveProtectionCategory() then
return ''
end


1. The non-iconified page protection template does not link [[Wikipedia:Request for edit]] when a page and its talk page are both protected as specified in the [[Protection_policy#Semi-protection|semi-protection policy]]. It would be better if that could happen automatically, maybe just add that always on talk pages when they are protected? If that's not possible, it would be good if there was an option to the template to add that link.
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


2. Several strings in the configuration state "or [[Special:UserLogin/signup|create an account]]" in reference to making edits to semi-protected pages. That seems misleading when a new account cannot edit semi-protected pages until it is old enough to be autoconfirmed.
-- Get the namespace key fragment.
local namespaceFragment = cfg.categoryNamespaceKeys[title.namespace]
if not namespaceFragment and title.namespace % 2 == 1 then
namespaceFragment = 'talk'
end


3. Would it be possible to change the default settings so that '''small=yes''' would be set by default on articles that are edit protected (not move protection or any kind of protection on talk pages, though). It seems like that's the preferred practice so why not make it the default?
-- 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}
}


[[User:Daniel Quinlan|Daniel Quinlan]] ([[User talk:Daniel Quinlan|talk]]) 21:53, 10 July 2023 (UTC)
--[[
-- 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.reasonsWithNamespacePriority[self.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
key[t.keypos] = 'all'
else
local quotient = i / 2 ^ (j - 1)
quotient = math.ceil(quotient)
if quotient % 2 == 1 then
key[t.keypos] = t.val
else
key[t.keypos] = 'all'
end
end
end
key = table.concat(key, '|')
local attempt = cats[key]
if attempt then
return makeCategoryLink(attempt, title.text)
end
end
return ''
end


== Template-editor move-protected? ==
function Protection:isIncorrect()
local expiry = self.expiry
return not self:shouldHaveProtectionCategory()
or type(expiry) == 'number' and expiry < os.time()
end


(Regarding [[Module:Protection banner/config#L-758]].)
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


It doesn't seem helpful to categorize a template into [[:Category:Wikipedia template-protected templates]] when it's only protected for ''moves'' and not for ''edits''. For example, [[Template:Bill Clinton]] is in that category but can be edited (but not moved) freely.
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


I suggest either (A) <s>removing that line</s> <ins>[edit: not adding any category]</ins>, similar to how currently there is no category for a page that is extended-confirmed move-protected but not edit-protected or (B) creating new categories [[:Category:Wikipedia template-editor move-protected templates]] and [[:Category:Wikipedia extended-confirmed move-protected pages]] (or similar). [[User:SilverLocust|<small style="color:#667;background:#fff;border:2px solid;border-radius:.4em;padding:0 .3em">SilverLocust</small>]] <small class="plainlinks sysop-show extendedmover-show">[//w.wiki/8xwX &#x1f0cf;]</small> [[User talk:SilverLocust|💬]] 02:26, 27 January 2024 (UTC)
--------------------------------------------------------------------------------
:{{reply to|SilverLocust}} That line was added by [[special:diff/976938143|this 00:07, 6 September 2020 edit]] per the discussion at [[#Move-protected]] above. – [[User:Wbm1058|wbm1058]] ([[User talk:Wbm1058|talk]]) 03:30, 8 February 2024 (UTC)
-- Blurb class
::To be more precise, that change wasn't added ''per'' the discussion at [[#Move-protected]], it was added ''followed by'' the discussion at [[#Move-protected]] that the change wasn't quite correct and should have some better solution. Nobody there claimed that the category now provided is correct, just that it's there so that it doesn't default to "fully protected pages".
--------------------------------------------------------------------------------
::But that just suggests that rather than removing the line entirely, option (A) would be to move that line up to the [[Module:Protection banner/config#L-735|2nd line]] within protectionCategories (so that it has lower priority than all of the categories below it) and to replace it with <syntaxhighlight lang="Lua">['all|template|all|templateeditor|move']    = '' -- there is currently no category for template-editor move-protected pages</syntaxhighlight>
::And I suppose there could be an option (C) — combining TE move-protected pages into [[:Category:Wikipedia move-protected templates]]. [[User:SilverLocust|<small style="color:#667;background:#fff;border:2px solid;border-radius:.4em;padding:0 .3em">SilverLocust</small>]] [[User talk:SilverLocust|💬]] 13:46, 27 February 2024 (UTC)
:::[[Module:Protection banner/config#L-734|The first line]] of  protectionCategories (which is the default) should also be changed to <syntaxhighlight lang="Lua">['all|all|all|all|all'] = 'Wikipedia miscellaneous protected pages',</syntaxhighlight> as a new tracking category, rather than defaulting to 'Wikipedia fully protected pages' when none of the below options match. And that would allow incorrect categories to just be removed rather than shoehorned into some not-quite-correct category. [[User:SilverLocust|<small style="color:#667;background:#fff;border:2px solid;border-radius:.4em;padding:0 .3em">SilverLocust</small>]] [[User talk:SilverLocust|💬]] 20:11, 27 February 2024 (UTC)


== Edit request 28 February 2024 ==
local Blurb = {}
Blurb.__index = Blurb


{{Edit template-protected|Module:Protection banner/config|answered=yes}}
Blurb.bannerTextFields = {
<syntaxhighlight lang="Lua> ['all|all|all|templateeditor|move']        = 'Wikipedia template-protected pages other than templates and modules',
text = true,
['all|template|all|extendedconfirmed|move'] = 'Wikipedia extended-confirmed-protected templates',</syntaxhighlight>
explanation = true,
Please [[Special:Diff/1210714229|add these two new lines (sandbox diff)]] to the protectionCategories object in the /config so that
tooltip = true,
# pages like the redirect {{no redirect|Template talk:Db-a1}} will not be miscategorized into [[:Category:Wikipedia fully protected pages]] (via {{tl|pp-move}}, which is called by {{tl|Redirect category shell}}) and
alt = true,
# extended-confirmed templates like [[Template:Israel–Hamas war]] will not be categorized into both [[:Category:Wikipedia extended-confirmed-protected pages]] (via {{tl|pp-move}}) and the subcategory [[:Category:Wikipedia extended-confirmed-protected templates]] (via {{tl|pp}}) rather than just the subcategory.
link = true
[[User:SilverLocust|<small style="color:#667;background:#fff;border:2px solid;border-radius:.4em;padding:0 .3em">SilverLocust</small>]] [[User talk:SilverLocust|💬]] 02:02, 28 February 2024 (UTC)
}


:I can make this edit now, so I shall do my own request. (This is separate from the suggestions in the previous section.) [[User:SilverLocust|<small style="color:#667;background:#fff;border:2px solid;border-radius:.4em;padding:0 .3em">SilverLocust</small>]] [[User talk:SilverLocust|💬]] 12:39, 1 March 2024 (UTC)
function Blurb.new(protectionObj, args, cfg)
return setmetatable({
_cfg = cfg,
_protectionObj = protectionObj,
_args = args
}, Blurb)
end


== Small bug ==
-- Private methods --


I noticed that a semi-protected article that has the extended-protection template applied to it displays the semi-protection icon, but with a tooltip that says "This article is semi-protected#semi". Not the biggest deal, but if anyone wants to get rid of the "#semi" at the end, that would be nice. <span style="border:3px outset;border-radius:8pt 0;padding:1px 5px;background:linear-gradient(6rad,#86c,#2b9)">[[User:Sdkb|<span style="color:#FFF;text-decoration:inherit;font:1em Lucida Sans">Sdkb</span>]]</span> <sup>[[User talk:Sdkb|'''talk''']]</sup> 21:09, 27 August 2024 (UTC)
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


:Which article? The anchor shouldn't be appearing since it should be part of the lock's link, and some quick testing myself doesn't seem to cause this case. [[User:Aidan9382|Aidan9382]] <sub>([[User talk:Aidan9382|talk]])</sub> 21:39, 27 August 2024 (UTC)
function Blurb:_getExpandedMessage(msgKey)
::I found it on [[Tim Walz]], which had EC protection that expired (and I then replaced with semi-protection). <span style="border:3px outset;border-radius:8pt 0;padding:1px 5px;background:linear-gradient(6rad,#86c,#2b9)">[[User:Sdkb|<span style="color:#FFF;text-decoration:inherit;font:1em Lucida Sans">Sdkb</span>]]</span> <sup>[[User talk:Sdkb|'''talk''']]</sup> 21:57, 27 August 2024 (UTC)
return self:_substituteParameters(self._cfg.msg[msgKey])
:::Seems fine to me, the tooltip is just "This article is semi-protected". [[User:Aidan9382|Aidan9382]] <sub>([[User talk:Aidan9382|talk]])</sub> 08:35, 28 August 2024 (UTC)
end


== Icon redirect ==
function Blurb:_substituteParameters(msg)
if not self._params then
local parameterFuncs = {}


Anyone watching? The BLACKLOCK icon at [[Asian News International vs. Wikimedia Foundation]] redirects to [[Wikipedia:Protection_policy#full]], but [[Wikipedia:Protection_policy#Office_actions]] would be more on point. [[User:Gråbergs Gråa Sång|Gråbergs Gråa Sång]] ([[User talk:Gråbergs Gråa Sång|talk]]) 18:41, 21 October 2024 (UTC)
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 parameterFuncs[k] then
param = parameterFuncs[k](self)
end
param = param or ''
t[k] = param
return param
end
})
end
msg = msg:gsub('${(%u+)}', self._params)
return msg
end


:This'll require a change to the module code, since the config responsible for determining the link that the padlock uses is determined [[Module:Protection banner/config#L-596|here]], and an office action is, in technical terms, just a normal full (admin) protection. The only way the difference is determined for the image is by explicitly using {{tl|pp-office}} instead of {{tl|pp}}, so something similar would need to be done for the image link handling too. [[User:Aidan9382|Aidan9382]] <sub>([[User talk:Aidan9382|talk]])</sub> 18:50, 21 October 2024 (UTC)
function Blurb:_makeCurrentVersionParameter()
::While office action protections are implemented as full protections, the purpose of the redirect is to provide additional context and information to readers. I agree that it makes sense for the icon to redirect to the most relevant section of the policy, {{slink|Wikipedia:Protection policy#Office actions}}. Just to be clear, I'm only commenting on the desired outcome, not on the technical implementation. [[User:Daniel Quinlan|Daniel Quinlan]] ([[User talk:Daniel Quinlan|talk]]) 18:58, 21 October 2024 (UTC)
-- A link to the page history or the move log, depending on the kind of
:::{{replyto|Gråbergs Gråa Sång}} It's easy to find out what the protection level is, and when it will expire. There are two [[WP:PF|parser functions]] that do this:
-- protection.
:::*<code><nowiki>{{PROTECTIONLEVEL:edit|Asian News International vs. Wikimedia Foundation}}</nowiki></code> → {{PROTECTIONLEVEL:edit|Asian News International vs. Wikimedia Foundation}}
local pagename = self._protectionObj.title.prefixedText
:::*<code><nowiki>{{PROTECTIONEXPIRY:edit|Asian News International vs. Wikimedia Foundation}}</nowiki></code> → {{PROTECTIONEXPIRY:edit|Asian News International vs. Wikimedia Foundation}}
if self._protectionObj.action == 'move' then
:::*<code><nowiki>{{PROTECTIONLEVEL:move|Asian News International vs. Wikimedia Foundation}}</nowiki></code> → {{PROTECTIONLEVEL:move|Asian News International vs. Wikimedia Foundation}}
-- We need the move log link.
:::*<code><nowiki>{{PROTECTIONEXPIRY:move|Asian News International vs. Wikimedia Foundation}}</nowiki></code> → {{PROTECTIONEXPIRY:move|Asian News International vs. Wikimedia Foundation}}
return makeFullUrl(
:::The value "sysop" denotes full protection, for any reason. These values are read directly from a table, and whereas this module obtains the values using another module (specifically [[Module:Effective protection level]]) and not the parser functions, the underlying routines in the MediaWiki software are the same, and so the values are read from the same table. But the ''reason'' for protection is not held in a table, it's only recorded as a log entry. The module would need to scan back through that log, since the imposition of protection might not be the most recent entry in the protection log (although in this case, [//en.wikipedia.org/wiki/Special:Log?type=protect&page=Asian_News_International_vs._Wikimedia_Foundation it is]). The module would also need to do that every time the page is retrieved, and that would take time. I don't think that this is a good idea. Stick with using {{tlx|pp-office}} or {{tlx|pp-vand}} etc., whichever is most appropriate, as these describe the situation explicitly. --[[User:Redrose64|<span style="color:#a80000; background:#ffeeee; text-decoration:inherit">Red</span>rose64]] &#x1f339; ([[User talk:Redrose64|talk]]) 07:37, 22 October 2024 (UTC)
'Special:Log',
::::If it can't be done, it can't be done. It would be a minor but obvious improvement for the reader, and my guess was that changing the "redirect" to [[WP:BLACKLOCK]] would be fairly trivial, though of course restricted by [[WP:PINKLOCK]] etc. To make a [[WP:OTHERCONTENT]] comparison, the [[WP:PINKLOCK]] icon (top right) at [[Module:Protection banner]] redirects to [[WP:PINKLOCK]], not [[WP:GOLDLOCK]]. [[User:Gråbergs Gråa Sång|Gråbergs Gråa Sång]] ([[User talk:Gråbergs Gråa Sång|talk]]) 07:48, 22 October 2024 (UTC)
{type = 'move', page = pagename},
:::::After a quick skim of the module code, it turns out it is already possible to override the link of the padlock in the config based on the template used, it's just none of the existing cases used said feature. I've tried my best to workshop the change in the sandbox config on phone [[Special:Diff/1252648173|here]]. [[User:Aidan9382|Aidan9382]] <sub>([[User talk:Aidan9382|talk]])</sub> 09:52, 22 October 2024 (UTC)
self:_getExpandedMessage('current-version-move-display')
::::::Nice find. That seems like a promising solution, and it uses a descriptive link. [[User:Daniel Quinlan|Daniel Quinlan]] ([[User talk:Daniel Quinlan|talk]]) 11:45, 22 October 2024 (UTC)
)
:::::It would be better to avoid using the color shortcuts which are often unclear to people in meaning and less frequently used, and instead use the more descriptive shortcuts like [[WP:ECP]] or, even better, direct and descriptive links. [[User:Daniel Quinlan|Daniel Quinlan]] ([[User talk:Daniel Quinlan|talk]]) 11:33, 22 October 2024 (UTC)
else
-- We need the history link.
return makeFullUrl(
pagename,
{action = 'history'},
self:_getExpandedMessage('current-version-edit-display')
)
end
end


If no one has any extra comments or suggestions regarding this, I'll move [[Special:Diff/1252648173|this change]] from the sandbox to main, since this seems pretty uncontroversial. [[User:Aidan9382|Aidan9382]] <sub>([[User talk:Aidan9382|talk]])</sub> 16:41, 22 October 2024 (UTC)
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')


:I've implemented the config change, office padlocks will now start linking to the office section in the protection policy. [[User:Aidan9382|Aidan9382]] <sub>([[User talk:Aidan9382|talk]])</sub> 18:54, 23 October 2024 (UTC)
return mEditRequest._link{type = requestType, display = display}
::Thanks! [[User:Gråbergs Gråa Sång|Gråbergs Gråa Sång]] ([[User talk:Gråbergs Gråa Sång|talk]]) 19:09, 23 October 2024 (UTC)
end
::Looks good. Office padlocks have the correct destination and full protection padlocks are unchanged. Thanks. [[User:Daniel Quinlan|Daniel Quinlan]] ([[User talk:Daniel Quinlan|talk]]) 19:19, 23 October 2024 (UTC)


== Mobile places infobox incorrectly for [[WP:PIA]] notice ==
function Blurb:_makeExpiryParameter()
local expiry = self._protectionObj.expiry
if type(expiry) == 'number' then
return self:_formatDate(expiry)
else
return expiry
end
end


Mobile moves infoboxes down after the opening paragraph per [[:mw:Reading/Web/Projects/Lead Paragraph Move]]. On [[Detention of Mahmoud Khalil]] {{tlx|1=pp-extended|2=small=yes}} includes <code><nowiki><p class='PIA-flag' style='display:none; visibility:hidden;'>This page is subject to the extended confirmed restriction related to the Arab-Israeli conflict.</p></nowiki></code>. The text is hidden but mobile still interprets it as an opening paragraph and displays the infobox right after it instead of moving it further down after the first displayed paragraph. Is there a better fix than moving {{tlx|1=pp-extended|2=small=yes}} down in the wikitext? [[MOS:ORDER]] says it should be at the top. [[User:PrimeHunter|PrimeHunter]] ([[User talk:PrimeHunter|talk]]) 09:36, 17 March 2025 (UTC)
function Blurb:_makeExplanationBlurbParameter()
-- Cover special cases first.
if self._protectionObj.title.namespace == 8 then
-- MediaWiki namespace
return self:_getExpandedMessage('explanation-blurb-nounprotect')
end


:According to the [https://gerrit.wikimedia.org/g/mediawiki/extensions/MobileFrontend/+/a7183b11e63dc28e6baabfc1eb9e62e02baab130/includes/Transforms/MoveLeadParagraphTransform.php#250 code] moving the notice inside a span might be enough. [[User:Sjoerddebruin|<span style="color:var(--color-progressive,#36c); font-weight:var(--font-weight-semi-bold,600); letter-spacing:0.05em;">Sjoerd de Bruin</span>]]&nbsp;[[Overleg gebruiker:Sjoerddebruin|<small style="color:var(--color-progressive,#36c); letter-spacing:0.05em;">({{int:Talkpagelinktext}})</small>]]  09:52, 17 March 2025 (UTC)
-- Get explanation blurb table keys
::That only targets <code><nowiki><span id="coordinates">...</span></nowiki></code>. We could use it but adding a coordinates id would be hacky. There may be tools looking for that id and getting confused. [[User:PrimeHunter|PrimeHunter]] ([[User talk:PrimeHunter|talk]]) 10:45, 17 March 2025 (UTC)
local action = self._protectionObj.action
:::It works in a mobile preview to use a div. [[User:PrimeHunter|PrimeHunter]] ([[User talk:PrimeHunter|talk]]) 18:35, 17 March 2025 (UTC)
local level = self._protectionObj.level
:This also means that this text, which exists only for the purpose of the abuse filter, is now present in multiple search engines where those articles are showing up: [https://duckduckgo.com/?t=ffab&q=detention+of+mahmoud+khalil&ia=web Duckduckgo example]. While I get why @[[User:SilverLocust|SilverLocust]] made that edit, they should’ve done it in a way that does not create this slew of issues. One way this could be mitigated is by testing whether adding that as a hidden {{tag|indicator|o}} instead would still work for those purposes, since those do not affect the page output in this way. [[user:stjn|stjn]] 17:31, 19 March 2025 (UTC)
local talkKey = self._protectionObj.title.isTalkPage and 'talk' or 'subject'
::I've made a [[Special:Diff/1281325360|fix]] (at least temporarily, someone else may have a better suggestion) for the mobile issue and I believe the search issue. Got to go, but I'll subscribe to this section. Email me if there's anything needing a quick response. - Jenson[[User:SilverLocust|SL]]&nbsp;([[User talk:SilverLocust|SilverLocust]]) 18:06, 19 March 2025 (UTC)


== Template-protected edit request on 19 December 2025 ==
-- Find the message in the explanation blurb table and substitute any
-- parameters.
local explanations = self._cfg.explanationBlurbs
local msg
if explanations[action][level] and explanations[action][level][talkKey] then
msg = explanations[action][level][talkKey]
elseif explanations[action][level] and explanations[action][level].default then
msg = explanations[action][level].default
elseif explanations[action].default and explanations[action].default[talkKey] then
msg = explanations[action].default[talkKey]
elseif explanations[action].default and explanations[action].default.default then
msg = explanations[action].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


{{edit template-protected|Module:Protection banner/config|answered=yes}}
function Blurb:_makeImageLinkParameter()
Let’s adapt “you may submit an edit request ask an administrator (or template editor)” based on whether the page is template-protected or fully protected. It’s not just administrators who can edit template-protected pages. [[User:2600 etc|2600 etc]] ([[User talk:2600 etc|talk]]) 01:29, 19 December 2025 (UTC)
local imageLinks = self._cfg.imageLinks
:{{not done for now}}''':'''<!-- Template:ETp --> from what I've seen, this has already been done. Are you able to show a specific example where a protected template's notice does not mention template editors? '''''[[User:Paine Ellsworth|<span style="font-size:92%;color:darkblue;font-family:Segoe Script">P.I.&nbsp;Ellsworth</span>]]'''''&thinsp;,&nbsp;[[Editor|<span style="color:black">ed.</span>]]&nbsp;–&nbsp;[[User talk:Paine Ellsworth|''welcome!'']]&nbsp;–&nbsp;<small>06:19, 19 December 2025 (UTC)</small>
local action = self._protectionObj.action
local level = self._protectionObj.level
local msg
if imageLinks[action][level] then
msg = imageLinks[action][level]
elseif imageLinks[action].default then
msg = imageLinks[action].default
else
msg = imageLinks.edit.default
end
return self:_substituteParameters(msg)
end


== Change all instances of [[Help:Protection]] to [[Wikipedia:Protection policy]] ==
function Blurb:_makeIntroBlurbParameter()
if self._protectionObj:isTemporary() then
return self:_getExpandedMessage('intro-blurb-expiry')
else
return self:_getExpandedMessage('intro-blurb-noexpiry')
end
end


[[Module:Protection banner/config]] contains links to [[Help:Protection]], which has barely been updated since 2008 and is much less useful to new editors than [[Wikipedia:Protection policy]], which contains information on specific protection types. I have demonstrated the changes in [[Module:Protection banner/config/sandbox]] ([https://en.wikipedia.org/w/index.php?title=Module%3AProtection_banner%2Fconfig%2Fsandbox&diff=1329273725&oldid=1254635270 diff]). We already link to [[Wikipedia:Protection policy]] for the vast majority of protected pages, [[Help:Protection]] is only displayed in banners.
function Blurb:_makeIntroFragmentParameter()
[[User:Helpful Raccoon|Helpful Raccoon]] ([[User talk:Helpful Raccoon|talk]]) 23:07, 24 December 2025 (UTC)
if self._protectionObj:isTemporary() then
return self:_getExpandedMessage('intro-fragment-expiry')
else
return self:_getExpandedMessage('intro-fragment-noexpiry')
end
end


== Expiration date not displaying in hover tooltip text on temporarily EC-protected pages ==
function Blurb:_makePagetypeParameter()
local pagetypes = self._cfg.pagetypes
return pagetypes[self._protectionObj.title.namespace]
or pagetypes.default
or error('no default pagetype defined', 8)
end


{{fyi|pointer=y}} Please see [[Template talk:Pp-extended#Expiration date not displaying in hover tooltip text on temporarily EC-protected pages]] for a bug with this module. --[[User:Redrose64|<span style="color:#a80000; background:#ffeeee; text-decoration:inherit">Red</span>rose64]] &#x1F98C; ([[User talk:Redrose64|talk]]) 12:30, 25 December 2025 (UTC)
function Blurb:_makeProtectionBlurbParameter()
local protectionBlurbs = self._cfg.protectionBlurbs
local action = self._protectionObj.action
local level = self._protectionObj.level
local msg
if protectionBlurbs[action][level] then
msg = protectionBlurbs[action][level]
elseif protectionBlurbs[action].default then
msg = protectionBlurbs[action].default
elseif protectionBlurbs.edit.default then
msg = protectionBlurbs.edit.default
else
error('no protection blurb defined for protectionBlurbs.edit.default', 8)
end
return self:_substituteParameters(msg)
end


:@[[User:Redrose64|Redrose64]] This can be fixed by either removing {{code|ecp}} as a protection reason from [[Module:Protection banner/config]], as I have demonstrated in [https://en.wikipedia.org/w/index.php?title=Module:Protection_banner/config/sandbox&diff=prev&oldid=1329429750 this diff], or just removing the tooltip from {{code|ecp}}. [[User:Helpful Raccoon|Helpful Raccoon]] ([[User talk:Helpful Raccoon|talk]]) 03:33, 26 December 2025 (UTC)
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 protectionLevels[action][level] then
msg = protectionLevels[action][level]
elseif protectionLevels[action].default then
msg = protectionLevels[action].default
elseif protectionLevels.edit.default then
msg = protectionLevels.edit.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.namespaces[self._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.bannerTextFields[key] then
error(string.format(
'"%s" is not a valid banner config field',
tostring(key)
), 2)
end
 
-- Generate the text.
local msg = self._protectionObj.bannerConfig[key]
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.indefImageReasons[reason]
)
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 images[action] then
if images[action][level] then
obj._imageFilename = images[action][level]
elseif images[action].default then
obj._imageFilename = images[action].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.padlockIndicatorNames[protectionObj.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
-- For arbitration enforcement, flagging [[WP:PIA]] pages to enable [[Special:AbuseFilter/1339]] to flag edits to them
if protectionObj.level == "extendedconfirmed" then
if require("Module:TableTools").inArray(protectionObj.title.talkPageTitle.categories, "Wikipedia pages subject to the extended confirmed restriction related to the Arab-Israeli conflict") then
ret[#ret + 1] = "<p class='PIA-flag' style='display:none; visibility:hidden;' title='This page is subject to the extended confirmed restriction related to the Arab-Israeli conflict.'></p>"
end
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.wrappers[parent: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
args[k] = v
end
for k, v in pairs(userArgs) do
args[k] = v
end
return p._main(args, cfg)
end
 
return p

Revision as of 16:56, 26 December 2025

Template:Module rating

This module creates protection banners and padlock icons that are placed at the top of protected pages. Most users will not need to use this module directly.

Usage

For adding protection templates to pages you can use the {{pp}} template, or you may find it more convenient to use one of the more specific protection templates in the table below.

Template:Protection templates

From wikitext

{{#invoke:Protection banner|main
| 1        = reason
| small    = yes/no
| action   = action
| date     = protection date
| user     = username
| section  = talk page section name
| category = yes
}}

The #invoke syntax can be used for creating protection templates more specific than {{pp}}. For example, it is possible to create a protection template which always shows a padlock icon by using the code {{#invoke:Protection banner|main|small=yes}}. Pages which call this template will still be able to use other arguments, like action. However, this only works one level deep; a page calling a template which calls another template containing the above code will not automatically be able to use parameters like action.

Note: You should no longer specify the expiry, as it is automatically retrieved in all cases.

From Lua

First, load the module.

local mProtectionBanner = require('Module:Protection banner')

Then you can make protection banners by using the _main function.

mProtectionBanner._main(args, cfg, titleObj)

args is a table of arguments to pass to the module. For possible keys and values for this table, see the parameters section. The cfg and titleObj variables are intended only for testing; cfg specifies a customised config table to use instead of Module:Protection banner/config, and titleObj specifies a mw.title object to use instead of the current title. args, cfg and titleObj are all optional.

Parameters

All parameters are optional.

  • 1 – the reason that the page was protected. If set, this must be one of the values listed in the reasons table.
  • small – if set to "yes", "y", "1", or "true", a padlock icon is generated instead of a full protection banner.
  • action – the protection action. Must be one of "edit" (for normal protection), "move" (for move-protection) or "autoreview" (for pending changes). The default value is "edit".
  • date – the protection date. This must be valid input to the second parameter of the #time parser function. This argument has an effect for reasons that use the PROTECTIONDATE parameter in their configuration. As of July 2014, those were the "office" and "reset" reasons.
  • user – the username of the user to generate links for. As of July 2014, this only has an effect when the "usertalk" reason is specified.
  • section – the section name of the protected page's talk page where discussion is taking place. This works for most, but not all, values of reason.
  • category – categories are suppressed if this is set to "no", "n", "0", or "false".
  • catonly – if set to "yes", "y", "1", or "true", will only return the protection categories, and not return the banner or padlock. This has no visible output.

Reasons

The following table contains the available reasons, plus the actions for which they are available.

Reason Action Description
blp edit For pages protected to promote compliance with the biographies of living persons policy
dispute edit For pages protected due to editing disputes
dmca edit For pages protected by the Wikimedia Foundation due to Digital Millennium Copyright Act takedown requests
ecp edit For articles in topic areas authorized by ArbCom or meets the criteria for community use
mainpage edit For pages protected for being displayed on the Main Page
office edit For pages protected by the Wikimedia Foundation
reset edit For pages protected by the Wikimedia Foundation and "reset" to a bare-bones version
sock edit For pages protected due to sock puppetry
template edit For high-risk templates and Lua modules
usertalk edit For pages protected against disruptive edits by a particular user
vandalism edit For pages protected against vandalism
dispute move For pages protected against page moves due to disputes over the page title
vandalism move For pages protected against page-move vandalism

Errors

Below is a list of some of the common errors that this module can produce, and how to fix them.

Invalid protection date

Template:Error

This error is produced if you supply a |date= parameter value that is not recognised as a valid date by the #time parser function. If in doubt, you can just use a date in the format "dd Month YYYY", e.g. "3 January 2026". To see a full range of valid inputs, see the #time documentation (only the first parameter, the format string, may be specified).

Invalid action

Template:Error

This error is produced if you specify an invalid protection action. There are only three valid actions: edit (the default, for normal protection), move (for move-protection), and autoreview (for pending changes). This should only be possible if you are using a template that supports manually specifying the protection action, such as {{pp}}, or if you are using #invoke directly. If this is not the case, please leave a message on Module talk:Protection banner.

Reasons cannot contain the pipe character

Template:Error

This error is produced if you specify a reason using the |1= parameter that includes a pipe character ("|"). Please check that you are not entering the {{!}} template into this parameter by mistake. The pipe character is disallowed as the module uses it internally. A list of valid reasons can be seen in the reasons section.

Other errors

If you see an error other than the ones above, it is likely to either be a bug in the module or mistake in the configuration. Please post a message about it at Module talk:Protection banner.

Technical details

This module uses configuration data from Module:Protection banner/config. Most of the module's behaviour can be configured there, making it easily portable across different wikis and different languages.

General test cases for the module can be found at Module:Protection banner/testcases, and test cases specific to enwiki's config can be found at Module:Protection banner/config/testcases.

Bug reports and feature requests should be made on the module's talk page.


-- 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.namespaces[14].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
		toWalk[k] = nil
		retval[k] = true
		for _,v in ipairs(graph[k]) do
			if not retval[v] then
				toWalk[v] = true
			end
		end
	end
end

--------------------------------------------------------------------------------
-- Protection class
--------------------------------------------------------------------------------

local Protection = {}
Protection.__index = Protection

Protection.supportedActions = {
	edit = 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.supportedActions[args.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 args[1] then
		obj.reason = mw.ustring.lower(args[1])
		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.banners[obj.action] then
			configTables[#configTables + 1] = cfg.banners[obj.action][obj.reason]
		end
		if cfg.defaultBanners[obj.action] then
			configTables[#configTables + 1] = cfg.defaultBanners[obj.action][obj.level]
			configTables[#configTables + 1] = cfg.defaultBanners[obj.action].default
		end
		configTables[#configTables + 1] = cfg.masterBanner
		for i, field in ipairs(Protection.bannerConfigFields) do
			for j, t in ipairs(configTables) do
				if t[field] then
					obj.bannerConfig[field] = t[field]
					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.categoryNamespaceKeys[title.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.reasonsWithNamespacePriority[self.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
				key[t.keypos] = 'all'
			else
				local quotient = i / 2 ^ (j - 1)
				quotient = math.ceil(quotient)
				if quotient % 2 == 1 then
					key[t.keypos] = t.val
				else
					key[t.keypos] = 'all'
				end
			end
		end
		key = table.concat(key, '|')
		local attempt = cats[key]
		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.msg[msgKey])
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 parameterFuncs[k] then
					param = parameterFuncs[k](self)
				end
				param = param or ''
				t[k] = 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 explanations[action][level] and explanations[action][level][talkKey] then
		msg = explanations[action][level][talkKey]
	elseif explanations[action][level] and explanations[action][level].default then
		msg = explanations[action][level].default
	elseif explanations[action].default and explanations[action].default[talkKey] then
		msg = explanations[action].default[talkKey]
	elseif explanations[action].default and explanations[action].default.default then
		msg = explanations[action].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 imageLinks[action][level] then
		msg = imageLinks[action][level]
	elseif imageLinks[action].default then
		msg = imageLinks[action].default
	else
		msg = imageLinks.edit.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 pagetypes[self._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 protectionBlurbs[action][level] then
		msg = protectionBlurbs[action][level]
	elseif protectionBlurbs[action].default then
		msg = protectionBlurbs[action].default
	elseif protectionBlurbs.edit.default then
		msg = protectionBlurbs.edit.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 protectionLevels[action][level] then
		msg = protectionLevels[action][level]
	elseif protectionLevels[action].default then
		msg = protectionLevels[action].default
	elseif protectionLevels.edit.default then
		msg = protectionLevels.edit.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.namespaces[self._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.bannerTextFields[key] then
		error(string.format(
			'"%s" is not a valid banner config field',
			tostring(key)
		), 2)
	end

	-- Generate the text.
	local msg = self._protectionObj.bannerConfig[key]
	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.indefImageReasons[reason]
			)
			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 images[action] then
				if images[action][level] then
					obj._imageFilename = images[action][level]
				elseif images[action].default then
					obj._imageFilename = images[action].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.padlockIndicatorNames[protectionObj.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
	
	-- For arbitration enforcement, flagging [[WP:PIA]] pages to enable [[Special:AbuseFilter/1339]] to flag edits to them
	if protectionObj.level == "extendedconfirmed" then
		if require("Module:TableTools").inArray(protectionObj.title.talkPageTitle.categories, "Wikipedia pages subject to the extended confirmed restriction related to the Arab-Israeli conflict") then
			ret[#ret + 1] = "<p class='PIA-flag' style='display:none; visibility:hidden;' title='This page is subject to the extended confirmed restriction related to the Arab-Israeli conflict.'></p>"
		end
	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.wrappers[parent: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
		args[k] = v
	end
	for k, v in pairs(userArgs) do
		args[k] = v
	end
	return p._main(args, cfg)
end

return p