From 79fa16b5c1f9166f30cedff482b77e8538f6c43a Mon Sep 17 00:00:00 2001 From: Tphamtranba <119475427+Bapham12@users.noreply.github.com> Date: Fri, 24 Apr 2026 20:07:05 +0700 Subject: [PATCH 1/4] Add Protection banner module with functionality --- Protection banner/main.lua | 898 +++++++++++++++++++++++++++++++++++++ 1 file changed, 898 insertions(+) create mode 100644 Protection banner/main.lua diff --git a/Protection banner/main.lua b/Protection banner/main.lua new file mode 100644 index 0000000..30ca757 --- /dev/null +++ b/Protection banner/main.lua @@ -0,0 +1,898 @@ +-- 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 '
' .. 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 + + 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 From a1c7d3338036c7b5c0fc3b26caf8343a8ebbae93 Mon Sep 17 00:00:00 2001 From: Tphamtranba <119475427+Bapham12@users.noreply.github.com> Date: Fri, 24 Apr 2026 20:07:58 +0700 Subject: [PATCH 2/4] Create config.lua --- Protection banner/config.lua | 1027 ++++++++++++++++++++++++++++++++++ 1 file changed, 1027 insertions(+) create mode 100644 Protection banner/config.lua diff --git a/Protection banner/config.lua b/Protection banner/config.lua new file mode 100644 index 0000000..5a2cde5 --- /dev/null +++ b/Protection banner/config.lua @@ -0,0 +1,1027 @@ +-- This module provides configuration data for [[Module:Protection banner]]. + +return { + +-------------------------------------------------------------------------------- +-- +-- BANNER DATA +-- +-------------------------------------------------------------------------------- + +--[[ +-- Banner data consists of six fields: +-- * text - the main protection text that appears at the top of protection +-- banners. +-- * explanation - the text that appears below the main protection text, used +-- to explain the details of the protection. +-- * tooltip - the tooltip text you see when you move the mouse over a small +-- padlock icon. +-- * link - the page that the small padlock icon links to. +-- * alt - the alt text for the small padlock icon. This is also used as tooltip +-- text for the large protection banners. +-- * image - the padlock image used in both protection banners and small padlock +-- icons. +-- +-- The module checks in three separate tables to find a value for each field. +-- First it checks the banners table, which has values specific to the reason +-- for the page being protected. Then the module checks the defaultBanners +-- table, which has values specific to each protection level. Finally, the +-- module checks the masterBanner table, which holds data for protection +-- templates to use if no data has been found in the previous two tables. +-- +-- The values in the banner data can take parameters. These are specified +-- using ${TEXTLIKETHIS} (a dollar sign preceding a parameter name +-- enclosed in curly braces). +-- +-- Available parameters: +-- +-- ${CURRENTVERSION} - a link to the page history or the move log, with the +-- display message "current-version-edit-display" or +-- "current-version-move-display". +-- +-- ${EDITREQUEST} - a link to create an edit request for the current page. +-- +-- ${EXPLANATIONBLURB} - an explanation blurb, e.g. "Please discuss any changes +-- on the talk page; you may submit a request to ask an administrator to make +-- an edit if it is minor or supported by consensus." +-- +-- ${IMAGELINK} - a link to set the image to, depending on the protection +-- action and protection level. +-- +-- ${INTROBLURB} - the PROTECTIONBLURB parameter, plus the expiry if an expiry +-- is set. E.g. "Editing of this page by new or unregistered users is currently +-- disabled until dd Month YYYY." +-- +-- ${INTROFRAGMENT} - the same as ${INTROBLURB}, but without final punctuation +-- so that it can be used in run-on sentences. +-- +-- ${PAGETYPE} - the type of the page, e.g. "article" or "template". +-- Defined in the cfg.pagetypes table. +-- +-- ${PROTECTIONBLURB} - a blurb explaining the protection level of the page, e.g. +-- "Editing of this page by new or unregistered users is currently disabled" +-- +-- ${PROTECTIONDATE} - the protection date, if it has been supplied to the +-- template. +-- +-- ${PROTECTIONLEVEL} - the protection level, e.g. "fully protected" or +-- "semi-protected". +-- +-- ${PROTECTIONLOG} - a link to the protection log or the pending changes log, +-- depending on the protection action. +-- +-- ${TALKPAGE} - a link to the talk page. If a section is specified, links +-- straight to that talk page section. +-- +-- ${TOOLTIPBLURB} - uses the PAGETYPE, PROTECTIONTYPE and EXPIRY parameters to +-- create a blurb like "This template is semi-protected", or "This article is +-- move-protected until DD Month YYYY". +-- +-- ${VANDAL} - links for the specified username (or the root page name) +-- using Module:Vandal-m. +-- +-- Functions +-- +-- For advanced users, it is possible to use Lua functions instead of strings +-- in the banner config tables. Using functions gives flexibility that is not +-- possible just by using parameters. Functions take two arguments, the +-- protection object and the template arguments, and they must output a string. +-- +-- For example: +-- +-- text = function (protectionObj, args) +-- if protectionObj.level == 'autoconfirmed' then +-- return 'foo' +-- else +-- return 'bar' +-- end +-- end +-- +-- Some protection object properties and methods that may be useful: +-- protectionObj.action - the protection action +-- protectionObj.level - the protection level +-- protectionObj.reason - the protection reason +-- protectionObj.expiry - the expiry. Nil if unset, the string "indef" if set +-- to indefinite, and the protection time in unix time if temporary. +-- protectionObj.protectionDate - the protection date in unix time, or nil if +-- unspecified. +-- protectionObj.bannerConfig - the banner config found by the module. Beware +-- of editing the config field used by the function, as it could create an +-- infinite loop. +-- protectionObj:isProtected - returns a boolean showing whether the page is +-- protected. +-- protectionObj:isTemporary - returns a boolean showing whether the expiry is +-- temporary. +-- protectionObj:isIncorrect - returns a boolean showing whether the protection +-- template is incorrect. +--]] + +-- The master banner data, used if no values have been found in banners or +-- defaultBanners. +masterBanner = { + text = '${INTROBLURB}', + explanation = '${EXPLANATIONBLURB}', + tooltip = '${TOOLTIPBLURB}', + link = '${IMAGELINK}', + alt = 'Page ${PROTECTIONLEVEL}' +}, + +-- The default banner data. This holds banner data for different protection +-- levels. +-- *required* - this table needs edit, move, autoreview and upload subtables. +defaultBanners = { + edit = {}, + move = {}, + autoreview = { + default = { + alt = 'Page protected with pending changes', + tooltip = 'All edits by unregistered and new users are subject to review prior to becoming visible to unregistered users', + image = 'Pending-protection-shackle.svg' + } + }, + upload = {} +}, + +-- The banner data. This holds banner data for different protection reasons. +-- In fact, the reasons specified in this table control which reasons are +-- valid inputs to the first positional parameter. +-- +-- There is also a non-standard "description" field that can be used for items +-- in this table. This is a description of the protection reason for use in the +-- module documentation. +-- +-- *required* - this table needs edit, move, autoreview and upload subtables. +banners = { + edit = { + blp = { + description = 'For pages protected to promote compliance with the' + .. ' [[BKDatabase:Biographies of living persons' + .. '|biographies of living persons]] policy', + text = '${INTROFRAGMENT} to promote compliance with' + .. ' [[BKDatabase:Biographies of living persons' + .. "|BKDatabase's policy on the biographies" + .. ' of living people]].', + tooltip = '${TOOLTIPFRAGMENT} to promote compliance with the policy on' + .. ' biographies of living persons', + }, + dmca = { + description = 'For pages protected by the Wikimedia Foundation' + .. ' due to [[Digital Millennium Copyright Act]] takedown requests', + explanation = function (protectionObj, args) + local ret = 'Pursuant to a rights owner notice under the Digital' + .. ' Millennium Copyright Act (DMCA) regarding some content' + .. ' in this article, the Wikimedia Foundation acted under' + .. ' applicable law and took down and restricted the content' + .. ' in question.' + if args.notice then + ret = ret .. ' A copy of the received notice can be found here: ' + .. args.notice .. '.' + end + ret = ret .. ' For more information, including websites discussing' + .. ' how to file a counter-notice, please see' + .. " [[BKDatabase:Office actions]] and the article's ${TALKPAGE}." + .. "'''Do not remove this template from the article until the" + .. " restrictions are withdrawn'''." + return ret + end, + image = 'Office-protection-shackle.svg', + }, + dispute = { + description = 'For pages protected due to editing disputes', + text = function (protectionObj, args) + -- Find the value of "disputes". + local display = 'disputes' + local disputes + if args.section then + disputes = string.format( + '[[%s:%s#%s|%s]]', + mw.site.namespaces[protectionObj.title.namespace].talk.name, + protectionObj.title.text, + args.section, + display + ) + else + disputes = display + end + + -- Make the blurb, depending on the expiry. + local msg + if type(protectionObj.expiry) == 'number' then + msg = '${INTROFRAGMENT} or until editing %s have been resolved.' + else + msg = '${INTROFRAGMENT} until editing %s have been resolved.' + end + return string.format(msg, disputes) + end, + explanation = "This protection is '''not''' an endorsement of the" + .. ' ${CURRENTVERSION}. ${EXPLANATIONBLURB}', + tooltip = '${TOOLTIPFRAGMENT} due to editing disputes', + }, + ecp = { + description = 'For articles in topic areas authorized by' + .. ' [[BKDatabase:Arbitration Committee|ArbCom]] or' + .. ' meets the criteria for community use', + tooltip = 'This ${PAGETYPE} is ${PROTECTIONLEVEL}', + alt = 'Extended-protected ${PAGETYPE}', + }, + mainpage = { + description = 'For pages protected for being displayed on the [[Main Page]]', + text = 'This file is currently' + .. ' [[BKDatabase:This page is protected|protected]] from' + .. ' editing because it is currently or will soon be displayed' + .. ' on the [[Main Page]].', + explanation = 'Images on the Main Page are protected due to their high' + .. ' visibility. Please discuss any necessary changes on the ${TALKPAGE}.' + .. '
' + .. "'''Administrators:''' Once this image is definitely off the Main Page," + .. ' please unprotect this file, or reduce to semi-protection,' + .. ' as appropriate.', + }, + office = { + description = 'For pages protected by the Wikimedia Foundation', + text = function (protectionObj, args) + local ret = 'This ${PAGETYPE} is currently under the' + .. ' scrutiny of the' + .. ' [[BKDatabase:Office actions|Wikimedia Foundation Office]]' + .. ' and is protected.' + if protectionObj.protectionDate then + ret = ret .. ' It has been protected since ${PROTECTIONDATE}.' + end + return ret + end, + explanation = "If you can edit this page, please discuss all changes and" + .. " additions on the ${TALKPAGE} first. '''Do not remove protection from this" + .. " page unless you are authorized by the Wikimedia Foundation to do" + .. " so.'''", + image = 'Office-protection-shackle.svg', + }, + reset = { + description = 'For pages protected by the Wikimedia Foundation and' + .. ' "reset" to a bare-bones version', + text = 'This ${PAGETYPE} is currently under the' + .. ' scrutiny of the' + .. ' [[BKDatabase:Office actions|Wikimedia Foundation Office]]' + .. ' and is protected.', + explanation = function (protectionObj, args) + local ret = '' + if protectionObj.protectionDate then + ret = ret .. 'On ${PROTECTIONDATE} this ${PAGETYPE} was' + else + ret = ret .. 'This ${PAGETYPE} has been' + end + ret = ret .. ' reduced to a' + .. ' simplified, "bare bones" version so that it may be completely' + .. ' rewritten to ensure it meets the policies of' + .. ' [[WP:NPOV|Neutral Point of View]] and [[WP:V|Verifiability]].' + .. ' Standard BKDatabase policies will apply to its rewriting—which' + .. ' will eventually be open to all editors—and will be strictly' + .. ' enforced. The ${PAGETYPE} has been ${PROTECTIONLEVEL} while' + .. ' it is being rebuilt.\n\n' + .. 'Any insertion of material directly from' + .. ' pre-protection revisions of the ${PAGETYPE} will be removed, as' + .. ' will any material added to the ${PAGETYPE} that is not properly' + .. ' sourced. The associated talk page(s) were also cleared on the' + .. " same date.\n\n" + .. "If you can edit this page, please discuss all changes and" + .. " additions on the ${TALKPAGE} first. '''Do not override" + .. " this action, and do not remove protection from this page," + .. " unless you are authorized by the Wikimedia Foundation" + .. " to do so. No editor may remove this notice.'''" + + return ret + end, + image = 'Office-protection-shackle.svg', + }, + sock = { + description = 'For pages protected due to' + .. ' [[BKDatabase:Sock puppetry|sock puppetry]]', + text = '${INTROFRAGMENT} to prevent [[BKDatabase:Sock puppetry|sock puppets]] of' + .. ' [[BKDatabase:Blocking policy|blocked]] or' + .. ' [[BKDatabase:Banning policy|banned users]]' + .. ' from editing it.', + tooltip = '${TOOLTIPFRAGMENT} to prevent sock puppets of blocked or banned users from' + .. ' editing it', + }, + template = { + description = 'For [[BKDatabase:High-risk templates|high-risk]]' + .. ' templates and Lua modules', + text = 'This is a permanently [[Help:Protection|protected]] ${PAGETYPE},' + .. ' as it is [[BKDatabase:High-risk templates|high-risk]].', + explanation = 'Please discuss any changes on the ${TALKPAGE}; you may' + .. ' ${EDITREQUEST} to ask an' + .. ' [[BKDatabase:Administrators|administrator]] or' + .. ' [[BKDatabase:Template editor|template editor]] to make an edit if' + .. ' it is [[Help:Minor edit#When to mark an edit as a minor edit' + .. '|uncontroversial]] or supported by' + .. ' [[BKDatabase:Consensus|consensus]]. You can also' + .. ' [[BKDatabase:Requests for page protection|request]] that the page be' + .. ' unprotected.', + tooltip = 'This high-risk ${PAGETYPE} is permanently ${PROTECTIONLEVEL}' + .. ' to prevent vandalism', + alt = 'Permanently protected ${PAGETYPE}', + }, + usertalk = { + description = 'For pages protected against disruptive edits by a' + .. ' particular user', + text = '${INTROFRAGMENT} to prevent ${VANDAL} from using it to make disruptive edits,' + .. ' such as abusing the' + .. ' {{[[Template:unblock|unblock]]}} template.', + explanation = 'If you cannot edit this user talk page and you need to' + .. ' make a change or leave a message, you can' + .. ' [[BKDatabase:Requests for page protection' + .. '#Current requests for edits to a protected page' + .. '|request an edit]],' + .. ' [[BKDatabase:Requests for page protection' + .. '#Current requests for reduction in protection level' + .. '|request unprotection]],' + .. ' [[Special:Userlogin|log in]],' + .. ' or [[Special:UserLogin/signup|create an account]].', + }, + vandalism = { + description = 'For pages protected against' + .. ' [[BKDatabase:Vandalism|vandalism]]', + text = '${INTROFRAGMENT} due to [[BKDatabase:Vandalism|vandalism]].', + explanation = function (protectionObj, args) + local ret = '' + if protectionObj.level == 'sysop' then + ret = ret .. "This protection is '''not''' an endorsement of the" + .. ' ${CURRENTVERSION}. ' + end + return ret .. '${EXPLANATIONBLURB}' + end, + tooltip = '${TOOLTIPFRAGMENT} due to vandalism', + } + }, + move = { + dispute = { + description = 'For pages protected against page moves due to' + .. ' disputes over the page title', + explanation = "This protection is '''not''' an endorsement of the" + .. ' ${CURRENTVERSION}. ${EXPLANATIONBLURB}', + image = 'Move-protection-shackle.svg' + }, + vandalism = { + description = 'For pages protected against' + .. ' [[BKDatabase:Vandalism#Page-move vandalism' + .. ' |page-move vandalism]]' + } + }, + autoreview = {}, + upload = {} +}, + +-------------------------------------------------------------------------------- +-- +-- GENERAL DATA TABLES +-- +-------------------------------------------------------------------------------- + +-------------------------------------------------------------------------------- +-- Protection blurbs +-------------------------------------------------------------------------------- + +-- This table produces the protection blurbs available with the +-- ${PROTECTIONBLURB} parameter. It is sorted by protection action and +-- protection level, and is checked by the module in the following order: +-- 1. page's protection action, page's protection level +-- 2. page's protection action, default protection level +-- 3. "edit" protection action, default protection level +-- +-- It is possible to use banner parameters inside this table. +-- *required* - this table needs edit, move, autoreview and upload subtables. +protectionBlurbs = { + edit = { + default = 'This ${PAGETYPE} is currently [[Help:Protection|' + .. 'protected]] from editing', + autoconfirmed = 'Editing of this ${PAGETYPE} by [[BKDatabase:User access' + .. ' levels#New users|new]] or [[BKDatabase:User access levels#Unregistered' + .. ' users|unregistered]] users is currently [[Help:Protection|disabled]]', + editextendedconfirmedprotected = 'This ${PAGETYPE} is currently under extended confirmed protection', + }, + move = { + default = 'This ${PAGETYPE} is currently [[Help:Protection|protected]]' + .. ' from [[Help:Moving a page|page moves]]' + }, + autoreview = { + default = 'All edits made to this ${PAGETYPE} by' + .. ' [[BKDatabase:User access levels#New users|new]] or' + .. ' [[BKDatabase:User access levels#Unregistered users|unregistered]]' + .. ' users are currently' + .. ' [[BKDatabase:Pending changes|subject to review]]' + }, + upload = { + default = 'Uploading new versions of this ${PAGETYPE} is currently disabled' + } +}, + + +-------------------------------------------------------------------------------- +-- Explanation blurbs +-------------------------------------------------------------------------------- + +-- This table produces the explanation blurbs available with the +-- ${EXPLANATIONBLURB} parameter. It is sorted by protection action, +-- protection level, and whether the page is a talk page or not. If the page is +-- a talk page it will have a talk key of "talk"; otherwise it will have a talk +-- key of "subject". The table is checked in the following order: +-- 1. page's protection action, page's protection level, page's talk key +-- 2. page's protection action, page's protection level, default talk key +-- 3. page's protection action, default protection level, page's talk key +-- 4. page's protection action, default protection level, default talk key +-- +-- It is possible to use banner parameters inside this table. +-- *required* - this table needs edit, move, autoreview and upload subtables. +explanationBlurbs = { + edit = { + autoconfirmed = { + subject = 'See the [[BKDatabase:Quy định khóa trang|' + .. 'Quy định khóa trang]] and ${PROTECTIONLOG} for more details. If you' + .. ' cannot edit this ${PAGETYPE} and you wish to make a change, you can' + .. ' ${EDITREQUEST}, discuss changes on the ${TALKPAGE},' + .. ' [[BKDatabase:Requests for page protection' + .. '#Current requests for reduction in protection level' + .. '|request unprotection]], [[Special:Userlogin|log in]], or' + .. ' [[Special:UserLogin/signup|create an account]].', + default = 'See the [[BKDatabase:Quy định khóa trang|' + .. 'Quy định khóa trang]] and ${PROTECTIONLOG} for more details. If you' + .. ' cannot edit this ${PAGETYPE} and you wish to make a change, you can' + .. ' [[BKDatabase:Requests for page protection' + .. '#Current requests for reduction in protection level' + .. '|request unprotection]], [[Special:Userlogin|log in]], or' + .. ' [[Special:UserLogin/signup|create an account]].', + }, + editextendedconfirmedprotected = { + default = 'Extended confirmed protection prevents edits from all unregistered editors' + .. ' and registered users with fewer than 30 days tenure and 500 edits.' + .. ' The [[BKDatabase:Quy định khóa trang#extended|policy on community use]]' + .. ' specifies that extended confirmed protection can be applied to combat' + .. ' disruption, if semi-protection has proven to be ineffective.' + .. ' Extended confirmed protection may also be applied to enforce' + .. ' [[BKDatabase:Arbitration Committee|arbitration sanctions]].' + .. ' Please discuss any changes on the ${TALKPAGE}; you may' + .. ' ${EDITREQUEST} to ask for uncontroversial changes supported by' + .. ' [[BKDatabase:Consensus|consensus]].' + }, + default = { + subject = 'See the [[BKDatabase:Quy định khóa trang|' + .. 'Quy định khóa trang]] and ${PROTECTIONLOG} for more details.' + .. ' Please discuss any changes on the ${TALKPAGE}; you' + .. ' may ${EDITREQUEST} to ask an' + .. ' [[BKDatabase:Administrators|administrator]] to make an edit if it' + .. ' is [[Help:Minor edit#When to mark an edit as a minor edit' + .. '|uncontroversial]] or supported by [[BKDatabase:Consensus' + .. '|consensus]]. You may also [[BKDatabase:Requests for' + .. ' page protection#Current requests for reduction in protection level' + .. '|request]] that this page be unprotected.', + default = 'See the [[BKDatabase:Quy định khóa trang|' + .. 'Quy định khóa trang]] and ${PROTECTIONLOG} for more details.' + .. ' You may [[BKDatabase:Requests for page' + .. ' protection#Current requests for edits to a protected page|request an' + .. ' edit]] to this page, or [[BKDatabase:Requests for' + .. ' page protection#Current requests for reduction in protection level' + .. '|ask]] for it to be unprotected.' + } + }, + move = { + default = { + subject = 'See the [[BKDatabase:Quy định khóa trang|' + .. 'Quy định khóa trang]] and ${PROTECTIONLOG} for more details.' + .. ' The page may still be edited but cannot be moved' + .. ' until unprotected. Please discuss any suggested moves on the' + .. ' ${TALKPAGE} or at [[BKDatabase:Requested moves]]. You can also' + .. ' [[BKDatabase:Requests for page protection|request]] that the page be' + .. ' unprotected.', + default = 'See the [[BKDatabase:Quy định khóa trang|' + .. 'Quy định khóa trang]] and ${PROTECTIONLOG} for more details.' + .. ' The page may still be edited but cannot be moved' + .. ' until unprotected. Please discuss any suggested moves at' + .. ' [[BKDatabase:Requested moves]]. You can also' + .. ' [[BKDatabase:Requests for page protection|request]] that the page be' + .. ' unprotected.' + } + }, + autoreview = { + default = { + default = 'See the [[BKDatabase:Quy định khóa trang|' + .. 'Quy định khóa trang]] and ${PROTECTIONLOG} for more details.' + .. ' Edits to this ${PAGETYPE} by new and unregistered users' + .. ' will not be visible to readers until they are accepted by' + .. ' a reviewer. To avoid the need for your edits to be' + .. ' reviewed, you may' + .. ' [[BKDatabase:Requests for page protection' + .. '#Current requests for reduction in protection level' + .. '|request unprotection]], [[Special:Userlogin|log in]], or' + .. ' [[Special:UserLogin/signup|create an account]].' + }, + }, + upload = { + default = { + default = 'See the [[BKDatabase:Quy định khóa trang|' + .. 'Quy định khóa trang]] and ${PROTECTIONLOG} for more details.' + .. ' The page may still be edited but new versions of the file' + .. ' cannot be uploaded until it is unprotected. You can' + .. ' request that a new version be uploaded by using a' + .. ' [[BKDatabase:Edit requests|protected edit request]], or you' + .. ' can [[BKDatabase:Requests for page protection|request]]' + .. ' that the file be unprotected.' + } + } +}, + +-------------------------------------------------------------------------------- +-- Protection levels +-------------------------------------------------------------------------------- + +-- This table provides the data for the ${PROTECTIONLEVEL} parameter, which +-- produces a short label for different protection levels. It is sorted by +-- protection action and protection level, and is checked in the following +-- order: +-- 1. page's protection action, page's protection level +-- 2. page's protection action, default protection level +-- 3. "edit" protection action, default protection level +-- +-- It is possible to use banner parameters inside this table. +-- *required* - this table needs edit, move, autoreview and upload subtables. +protectionLevels = { + edit = { + default = 'protected', + edittemplateeditorprotected = 'template-protected', + editextendedconfirmedprotected = 'extended-protected', + autoconfirmed = 'semi-protected', + editbureaucratprotected = 'bureaucrat-protected', + editmoderatorprotected = 'moderator-protected' + }, + move = { + default = 'move-protected' + }, + autoreview = { + }, + upload = { + default = 'upload-protected' + } +}, + +-------------------------------------------------------------------------------- +-- Images +-------------------------------------------------------------------------------- + +-- This table lists different padlock images for each protection action and +-- protection level. It is used if an image is not specified in any of the +-- banner data tables, and if the page does not satisfy the conditions for using +-- the ['image-filename-indef'] image. It is checked in the following order: +-- 1. page's protection action, page's protection level +-- 2. page's protection action, default protection level +images = { + edit = { + default = 'Full-protection-shackle.svg', + edittemplateeditorprotected = 'Template-protection-shackle-brackets_2.svg', + editextendedconfirmedprotected = 'Extended-protection-shackle.svg', + autoconfirmed = 'Semi-protection-shackle.svg', + editbureaucratprotected = 'Full-protection-shackle-block.svg', + editmoderatorprotected = 'Full-protection-shackle-A.svg' + }, + move = { + default = 'Move-protection-shackle.svg', + }, + autoreview = { + default = 'Pending-protection-shackle.svg' + }, + upload = { + default = 'Upload-protection-shackle.svg' + } +}, + +-- Pages with a reason specified in this table will show the special "indef" +-- padlock, defined in the 'image-filename-indef' message, if no expiry is set. +indefImageReasons = { + template = true +}, + +-------------------------------------------------------------------------------- +-- Image links +-------------------------------------------------------------------------------- + +-- This table provides the data for the ${IMAGELINK} parameter, which gets +-- the image link for small padlock icons based on the page's protection action +-- and protection level. It is checked in the following order: +-- 1. page's protection action, page's protection level +-- 2. page's protection action, default protection level +-- 3. "edit" protection action, default protection level +-- +-- It is possible to use banner parameters inside this table. +-- *required* - this table needs edit, move, autoreview and upload subtables. +imageLinks = { + edit = { + default = 'BKDatabase:Quy định khóa trang#Khóa bảo quản viên', + edittemplateeditorprotected = 'BKDatabase:Quy định khóa trang#Khóa bản mẫu', + editextendedconfirmedprotected = 'BKDatabase:Quy định khóa trang#Khóa mở rộng', + autoconfirmed = 'BKDatabase:Quy định khóa trang#Khóa tự động xác nhận', + editbureaucratprotected = 'BKDatabase:Quy định khóa trang#Khóa hành chính viên', + editmoderatorprotected = 'BKDatabase:Quy định khóa trang#Khóa điều phối viên' + }, + move = { + default = 'BKDatabase:Quy định khóa trang#di chuyển' + }, + autoreview = { + default = 'BKDatabase:Quy định khóa trang#đang tiến hành' + }, + upload = { + default = 'BKDatabase:Quy định khóa trang#tải lên' + } +}, + +-------------------------------------------------------------------------------- +-- Padlock indicator names +-------------------------------------------------------------------------------- + +-- This table provides the "name" attribute for the extension tag +-- with which small padlock icons are generated. All indicator tags on a page +-- are displayed in alphabetical order based on this attribute, and with +-- indicator tags with duplicate names, the last tag on the page wins. +-- The attribute is chosen based on the protection action; table keys must be a +-- protection action name or the string "default". +padlockIndicatorNames = { + autoreview = 'pp-autoreview', + default = 'pp-default' +}, + +-------------------------------------------------------------------------------- +-- Protection categories +-------------------------------------------------------------------------------- + +--[[ +-- The protection categories are stored in the protectionCategories table. +-- Keys to this table are made up of the following strings: +-- +-- 1. the expiry date +-- 2. the namespace +-- 3. the protection reason (e.g. "dispute" or "vandalism") +-- 4. the protection level (e.g. "sysop" or "autoconfirmed") +-- 5. the action (e.g. "edit" or "move") +-- +-- When the module looks up a category in the table, first it will will check to +-- see a key exists that corresponds to all five parameters. For example, a +-- user page semi-protected from vandalism for two weeks would have the key +-- "temp-user-vandalism-autoconfirmed-edit". If no match is found, the module +-- changes the first part of the key to "all" and checks the table again. It +-- keeps checking increasingly generic key combinations until it finds the +-- field, or until it reaches the key "all-all-all-all-all". +-- +-- The module uses a binary matrix to determine the order in which to search. +-- This is best demonstrated by a table. In this table, the "0" values +-- represent "all", and the "1" values represent the original data (e.g. +-- "indef" or "file" or "vandalism"). +-- +-- expiry namespace reason level action +-- order +-- 1 1 1 1 1 1 +-- 2 0 1 1 1 1 +-- 3 1 0 1 1 1 +-- 4 0 0 1 1 1 +-- 5 1 1 0 1 1 +-- 6 0 1 0 1 1 +-- 7 1 0 0 1 1 +-- 8 0 0 0 1 1 +-- 9 1 1 1 0 1 +-- 10 0 1 1 0 1 +-- 11 1 0 1 0 1 +-- 12 0 0 1 0 1 +-- 13 1 1 0 0 1 +-- 14 0 1 0 0 1 +-- 15 1 0 0 0 1 +-- 16 0 0 0 0 1 +-- 17 1 1 1 1 0 +-- 18 0 1 1 1 0 +-- 19 1 0 1 1 0 +-- 20 0 0 1 1 0 +-- 21 1 1 0 1 0 +-- 22 0 1 0 1 0 +-- 23 1 0 0 1 0 +-- 24 0 0 0 1 0 +-- 25 1 1 1 0 0 +-- 26 0 1 1 0 0 +-- 27 1 0 1 0 0 +-- 28 0 0 1 0 0 +-- 29 1 1 0 0 0 +-- 30 0 1 0 0 0 +-- 31 1 0 0 0 0 +-- 32 0 0 0 0 0 +-- +-- In this scheme the action has the highest priority, as it is the last +-- to change, and the expiry has the least priority, as it changes the most. +-- The priorities of the expiry, the protection level and the action are +-- fixed, but the priorities of the reason and the namespace can be swapped +-- through the use of the cfg.bannerDataNamespaceHasPriority table. +--]] + +-- If the reason specified to the template is listed in this table, +-- namespace data will take priority over reason data in the protectionCategories +-- table. +reasonsWithNamespacePriority = { + vandalism = true, +}, + +-- The string to use as a namespace key for the protectionCategories table for each +-- namespace number. +categoryNamespaceKeys = { + [ 2] = 'user', + [ 3] = 'user', + [ 4] = 'project', + [ 6] = 'file', + [ 8] = 'mediawiki', + [ 10] = 'template', + [ 12] = 'project', + [ 14] = 'category', + [100] = 'portal', + [828] = 'module', +}, + +protectionCategories = { + ['all|all|all|all|all'] = 'BKDatabase fully protected pages', + ['all|all|office|all|all'] = 'BKDatabase Office-protected pages', + ['all|all|reset|all|all'] = 'BKDatabase Office-protected pages', + ['all|all|dmca|all|all'] = 'BKDatabase Office-protected pages', + ['all|all|mainpage|all|all'] = 'BKDatabase fully protected main page files', + ['all|all|all|editextendedconfirmedprotected|all'] = 'BKDatabase extended-confirmed-protected pages', + ['all|all|ecp|editextendedconfirmedprotected|all'] = 'BKDatabase extended-confirmed-protected pages', + ['all|template|all|all|edit'] = 'BKDatabase fully protected templates', + ['all|all|all|autoconfirmed|edit'] = 'BKDatabase semi-protected pages', + ['indef|all|all|autoconfirmed|edit'] = 'BKDatabase indefinitely semi-protected pages', + ['all|all|blp|autoconfirmed|edit'] = 'BKDatabase indefinitely semi-protected biographies of living people', + ['temp|all|blp|autoconfirmed|edit'] = 'BKDatabase temporarily semi-protected biographies of living people', + ['all|all|dispute|autoconfirmed|edit'] = 'BKDatabase pages semi-protected due to dispute', + ['all|all|sock|autoconfirmed|edit'] = 'BKDatabase pages semi-protected from banned users', + ['all|all|vandalism|autoconfirmed|edit'] = 'BKDatabase pages semi-protected against vandalism', + ['all|category|all|autoconfirmed|edit'] = 'BKDatabase semi-protected categories', + ['all|file|all|autoconfirmed|edit'] = 'BKDatabase semi-protected files', + ['all|portal|all|autoconfirmed|edit'] = 'BKDatabase semi-protected portals', + ['all|project|all|autoconfirmed|edit'] = 'BKDatabase semi-protected project pages', + ['all|talk|all|autoconfirmed|edit'] = 'BKDatabase semi-protected talk pages', + ['all|template|all|autoconfirmed|edit'] = 'BKDatabase semi-protected templates', + ['all|user|all|autoconfirmed|edit'] = 'BKDatabase semi-protected user and user talk pages', + ['all|all|all|edittemplateeditorprotected|edit'] = 'BKDatabase template-protected pages other than templates and modules', + ['all|template|all|edittemplateeditorprotected|edit'] = 'BKDatabase template-protected templates', + ['all|template|all|edittemplateeditorprotected|move'] = 'BKDatabase template-protected templates', -- move-protected templates + ['all|all|blp|sysop|edit'] = 'BKDatabase indefinitely protected biographies of living people', + ['temp|all|blp|sysop|edit'] = 'BKDatabase temporarily protected biographies of living people', + ['all|all|dispute|sysop|edit'] = 'BKDatabase pages protected due to dispute', + ['all|all|sock|sysop|edit'] = 'BKDatabase pages protected from banned users', + ['all|all|vandalism|sysop|edit'] = 'BKDatabase pages protected against vandalism', + ['all|category|all|sysop|edit'] = 'BKDatabase fully protected categories', + ['all|file|all|sysop|edit'] = 'BKDatabase fully protected files', + ['all|project|all|sysop|edit'] = 'BKDatabase fully protected project pages', + ['all|talk|all|sysop|edit'] = 'BKDatabase fully protected talk pages', + ['all|template|all|editextendedconfirmedprotected|edit'] = 'BKDatabase extended-confirmed-protected templates', + ['all|template|all|sysop|edit'] = 'BKDatabase fully protected templates', + ['all|user|all|sysop|edit'] = 'BKDatabase fully protected user and user talk pages', + ['all|module|all|all|edit'] = 'BKDatabase fully protected modules', + ['all|module|all|edittemplateeditorprotected|edit'] = 'BKDatabase template-protected modules', + ['all|module|all|editextendedconfirmedprotected|edit'] = 'BKDatabase extended-confirmed-protected modules', + ['all|module|all|autoconfirmed|edit'] = 'BKDatabase semi-protected modules', + ['all|all|all|sysop|move'] = 'BKDatabase move-protected pages', + ['indef|all|all|sysop|move'] = 'BKDatabase indefinitely move-protected pages', + ['all|all|dispute|sysop|move'] = 'BKDatabase pages move-protected due to dispute', + ['all|all|vandalism|sysop|move'] = 'BKDatabase pages move-protected due to vandalism', + ['all|portal|all|sysop|move'] = 'BKDatabase move-protected portals', + ['all|project|all|sysop|move'] = 'BKDatabase move-protected project pages', + ['all|talk|all|sysop|move'] = 'BKDatabase move-protected talk pages', + ['all|template|all|sysop|move'] = 'BKDatabase move-protected templates', + ['all|user|all|sysop|move'] = 'BKDatabase move-protected user and user talk pages', + ['all|all|all|autoconfirmed|autoreview'] = 'BKDatabase pending changes protected pages', + ['all|file|all|all|upload'] = 'BKDatabase upload-protected files', +}, + +-------------------------------------------------------------------------------- +-- Expiry category config +-------------------------------------------------------------------------------- + +-- This table configures the expiry category behaviour for each protection +-- action. +-- * If set to true, setting that action will always categorise the page if +-- an expiry parameter is not set. +-- * If set to false, setting that action will never categorise the page. +-- * If set to nil, the module will categorise the page if: +-- 1) an expiry parameter is not set, and +-- 2) a reason is provided, and +-- 3) the specified reason is not blacklisted in the reasonsWithoutExpiryCheck +-- table. + +expiryCheckActions = { + edit = nil, + move = false, + autoreview = true, + upload = false +}, + +reasonsWithoutExpiryCheck = { + blp = true, + template = true, +}, + +-------------------------------------------------------------------------------- +-- Pagetypes +-------------------------------------------------------------------------------- + +-- This table produces the page types available with the ${PAGETYPE} parameter. +-- Keys are namespace numbers, or the string "default" for the default value. +pagetypes = { + [0] = 'article', + [6] = 'file', + [10] = 'template', + [14] = 'category', + [828] = 'module', + default = 'page' +}, + +-------------------------------------------------------------------------------- +-- Strings marking indefinite protection +-------------------------------------------------------------------------------- + +-- This table contains values passed to the expiry parameter that mean the page +-- is protected indefinitely. +indefStrings = { + ['indef'] = true, + ['indefinite'] = true, + ['indefinitely'] = true, + ['infinite'] = true, +}, + +-------------------------------------------------------------------------------- +-- Group hierarchy +-------------------------------------------------------------------------------- + +-- This table maps each group to all groups that have a superset of the original +-- group's page editing permissions. +hierarchy = { + sysop = {}, + reviewer = {'sysop'}, + filemover = {'sysop'}, + edittemplateeditorprotected = {'sysop'}, + editextendedconfirmedprotected = {'sysop'}, + autoconfirmed = {'reviewer', 'filemover', 'edittemplateeditorprotected', 'editextendedconfirmedprotected'}, + user = {'autoconfirmed'}, + ['*'] = {'user'} +}, + +-------------------------------------------------------------------------------- +-- Wrapper templates and their default arguments +-------------------------------------------------------------------------------- + +-- This table contains wrapper templates used with the module, and their +-- default arguments. Templates specified in this table should contain the +-- following invocation, and no other template content: +-- +-- {{#invoke:Protection banner|main}} +-- +-- If other content is desired, it can be added between +-- ... tags. +-- +-- When a user calls one of these wrapper templates, they will use the +-- default arguments automatically. However, users can override any of the +-- arguments. +wrappers = { + ['Template:Pp'] = {}, + ['Template:Pp-extended'] = {'ecp'}, + ['Template:Pp-blp'] = {'blp'}, + -- we don't need Template:Pp-create + ['Template:Pp-dispute'] = {'dispute'}, + ['Template:Pp-main-page'] = {'mainpage'}, + ['Template:Pp-move'] = {action = 'move', catonly = 'yes'}, + ['Template:Pp-move-dispute'] = {'dispute', action = 'move', catonly = 'yes'}, + -- we don't need Template:Pp-move-indef + ['Template:Pp-move-vandalism'] = {'vandalism', action = 'move', catonly = 'yes'}, + ['Template:Pp-office'] = {'office'}, + ['Template:Pp-office-dmca'] = {'dmca'}, + ['Template:Pp-pc'] = {action = 'autoreview', small = true}, + ['Template:Pp-pc1'] = {action = 'autoreview', small = true}, + ['Template:Pp-reset'] = {'reset'}, + ['Template:Pp-semi-indef'] = {small = true}, + ['Template:Pp-sock'] = {'sock'}, + ['Template:Pp-template'] = {'template', small = true}, + ['Template:Pp-upload'] = {action = 'upload'}, + ['Template:Pp-usertalk'] = {'usertalk'}, + ['Template:Pp-vandalism'] = {'vandalism'}, +}, + +-------------------------------------------------------------------------------- +-- +-- MESSAGES +-- +-------------------------------------------------------------------------------- + +msg = { + +-------------------------------------------------------------------------------- +-- Intro blurb and intro fragment +-------------------------------------------------------------------------------- + +-- These messages specify what is produced by the ${INTROBLURB} and +-- ${INTROFRAGMENT} parameters. If the protection is temporary they use the +-- intro-blurb-expiry or intro-fragment-expiry, and if not they use +-- intro-blurb-noexpiry or intro-fragment-noexpiry. +-- It is possible to use banner parameters in these messages. +['intro-blurb-expiry'] = '${PROTECTIONBLURB} until ${EXPIRY}.', +['intro-blurb-noexpiry'] = '${PROTECTIONBLURB}.', +['intro-fragment-expiry'] = '${PROTECTIONBLURB} until ${EXPIRY},', +['intro-fragment-noexpiry'] = '${PROTECTIONBLURB}', + +-------------------------------------------------------------------------------- +-- Tooltip blurb +-------------------------------------------------------------------------------- + +-- These messages specify what is produced by the ${TOOLTIPBLURB} parameter. +-- If the protection is temporary the tooltip-blurb-expiry message is used, and +-- if not the tooltip-blurb-noexpiry message is used. +-- It is possible to use banner parameters in these messages. +['tooltip-blurb-expiry'] = 'This ${PAGETYPE} is ${PROTECTIONLEVEL} until ${EXPIRY}.', +['tooltip-blurb-noexpiry'] = 'This ${PAGETYPE} is ${PROTECTIONLEVEL}.', +['tooltip-fragment-expiry'] = 'This ${PAGETYPE} is ${PROTECTIONLEVEL} until ${EXPIRY},', +['tooltip-fragment-noexpiry'] = 'This ${PAGETYPE} is ${PROTECTIONLEVEL}', + +-------------------------------------------------------------------------------- +-- Special explanation blurb +-------------------------------------------------------------------------------- + +-- An explanation blurb for pages that cannot be unprotected, e.g. for pages +-- in the MediaWiki namespace. +-- It is possible to use banner parameters in this message. +['explanation-blurb-nounprotect'] = 'See the [[BKDatabase:Quy định khóa trang|' + .. 'Quy định khóa trang]] and ${PROTECTIONLOG} for more details.' + .. ' Please discuss any changes on the ${TALKPAGE}; you' + .. ' may ${EDITREQUEST} to ask an' + .. ' [[BKDatabase:Administrators|administrator]] to make an edit if it' + .. ' is [[Help:Minor edit#When to mark an edit as a minor edit' + .. '|uncontroversial]] or supported by [[BKDatabase:Consensus' + .. '|consensus]].', + +-------------------------------------------------------------------------------- +-- Protection log display values +-------------------------------------------------------------------------------- + +-- These messages determine the display values for the protection log link +-- or the pending changes log link produced by the ${PROTECTIONLOG} parameter. +-- It is possible to use banner parameters in these messages. +['protection-log-display'] = 'protection log', +['pc-log-display'] = 'pending changes log', + +-------------------------------------------------------------------------------- +-- Current version display values +-------------------------------------------------------------------------------- + +-- These messages determine the display values for the page history link +-- or the move log link produced by the ${CURRENTVERSION} parameter. +-- It is possible to use banner parameters in these messages. +['current-version-move-display'] = 'current title', +['current-version-edit-display'] = 'current version', + +-------------------------------------------------------------------------------- +-- Talk page +-------------------------------------------------------------------------------- + +-- This message determines the display value of the talk page link produced +-- with the ${TALKPAGE} parameter. +-- It is possible to use banner parameters in this message. +['talk-page-link-display'] = 'talk page', + +-------------------------------------------------------------------------------- +-- Edit requests +-------------------------------------------------------------------------------- + +-- This message determines the display value of the edit request link produced +-- with the ${EDITREQUEST} parameter. +-- It is possible to use banner parameters in this message. +['edit-request-display'] = 'submit an edit request', + +-------------------------------------------------------------------------------- +-- Expiry date format +-------------------------------------------------------------------------------- + +-- This is the format for the blurb expiry date. It should be valid input for +-- the first parameter of the #time parser function. +['expiry-date-format'] = 'F j, Y "at" H:i e', + +-------------------------------------------------------------------------------- +-- Tracking categories +-------------------------------------------------------------------------------- + +-- These messages determine which tracking categories the module outputs. +['tracking-category-incorrect'] = 'BKDatabase pages with incorrect protection templates', +['tracking-category-template'] = 'BKDatabase template-protected pages other than templates and modules', + +-------------------------------------------------------------------------------- +-- Images +-------------------------------------------------------------------------------- + +-- These are images that are not defined by their protection action and protection level. +['image-filename-indef'] = 'Full-protection-shackle.svg', +['image-filename-default'] = 'Transparent.gif', + +-------------------------------------------------------------------------------- +-- End messages +-------------------------------------------------------------------------------- +} + +-------------------------------------------------------------------------------- +-- End configuration +-------------------------------------------------------------------------------- +} From bacc50fb16fbcaddab141b314475197349019923 Mon Sep 17 00:00:00 2001 From: Tphamtranba <119475427+Bapham12@users.noreply.github.com> Date: Fri, 24 Apr 2026 20:08:41 +0700 Subject: [PATCH 3/4] Add documentation generation for Protection banner This module generates documentation for the Protection banner, including a reason table sorted by action and reason. --- Protection banner/documentation/main.lua | 87 ++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 Protection banner/documentation/main.lua diff --git a/Protection banner/documentation/main.lua b/Protection banner/documentation/main.lua new file mode 100644 index 0000000..485002c --- /dev/null +++ b/Protection banner/documentation/main.lua @@ -0,0 +1,87 @@ +-- This module generates documentation for [[Module:Protection banner]]. + +-------------------------------------------------------------------------------- +-- Documentation class +-------------------------------------------------------------------------------- + +local Documentation = {} +Documentation.__index = Documentation + +function Documentation:new(mainCfg, docCfg) + return setmetatable({ + _mainCfg = mainCfg, + _docCfg = docCfg + }, self) +end + +function Documentation:makeReasonTable() + -- Get the data from the cfg.banners table. + local rowData = {} + for action, reasonTables in pairs(self._mainCfg.banners) do + for reason, t in pairs(reasonTables) do + rowData[#rowData + 1] = { + reason = reason, + action = action, + description = t.description + } + end + end + + -- Sort the table into alphabetical order, first by action and then by + -- reason. + table.sort(rowData, function (t1, t2) + if t1.action == t2.action then + return t1.reason < t2.reason + else + return t1.action < t2.action + end + end) + + -- Assemble a wikitable of the data. + local ret = {} + ret[#ret + 1] = '{| class="wikitable"' + if #rowData < 1 then + ret[#ret + 1] = '|-' + ret[#ret + 1] = string.format( + '| colspan="3" | %s', + self._docCfg['documentation-blurb-noreasons'] + ) + else + -- Header + ret[#ret + 1] = '|-' + ret[#ret + 1] = string.format( + '! %s\n! %s\n! %s', + self._docCfg['documentation-heading-reason'], + self._docCfg['documentation-heading-action'], + self._docCfg['documentation-heading-description'] + ) + -- Rows + for _, t in ipairs(rowData) do + ret[#ret + 1] = '|-' + ret[#ret + 1] = string.format( + '| %s\n| %s\n| %s', + t.reason, + t.action, + t.description or '' + ) + end + end + ret[#ret + 1] = '|}' + + return table.concat(ret, '\n') +end + +-------------------------------------------------------------------------------- +-- Exports +-------------------------------------------------------------------------------- + +local p = {} + +function p.reasonTable() + local mainCfg = require('Mô đun:Protection banner/config') + local docCfg = require('Mô đun:Protection banner/documentation/config') + local documentationObj = Documentation:new(mainCfg, docCfg) + return documentationObj:makeReasonTable() +end + +return p From 1b8eaba90ed57ea1fcf563a27312de455684f3d8 Mon Sep 17 00:00:00 2001 From: Tphamtranba <119475427+Bapham12@users.noreply.github.com> Date: Fri, 24 Apr 2026 20:09:19 +0700 Subject: [PATCH 4/4] Add config.lua for documentation messages --- Protection banner/documentation/config.lua | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Protection banner/documentation/config.lua diff --git a/Protection banner/documentation/config.lua b/Protection banner/documentation/config.lua new file mode 100644 index 0000000..dff81cf --- /dev/null +++ b/Protection banner/documentation/config.lua @@ -0,0 +1,15 @@ +-- This module contains messages used to generate the documentation for +-- [[Module:Protection banner]]. + +return { + +-- Reason table headings. +['documentation-heading-reason'] = 'Lý do', +['documentation-heading-action'] = 'Hành động', +['documentation-heading-description'] = 'Miêu tả', + +-- Blurb to display if no reasons were found in the module config. +['documentation-blurb-noreasons'] = 'Không tìm thấy giá trị lý do trong mô đun' + .. ' thiết lập.', + +}