경고: 이 문서의 오래된 판을 편집하고 있습니다. 이것을 게시하면, 이 판 이후로 바뀐 모든 편집이 사라집니다. 귀하는 로그인되어 있지 않습니다. 이대로 편집하면 귀하의 IP 주소가 편집 기록에 남게 됩니다.스팸 방지 검사입니다. 이것을 입력하지 마세요!-- This module implements {{pp-meta}} and its daughter templates such as -- {{pp-dispute}}, {{pp-vandalism}} and {{pp-sock}}. -- Initialise necessary modules. require('Module:No globals') local class = require('Module:Middleclass').class local newFileLink = require('Module:File link').new local effectiveProtectionLevel = require('Module:Effective protection level')._main local yesno = require('Module:Yesno') -- Lazily initialise modules and objects we don't always need. local getArgs, makeMessageBox, lang -------------------------------------------------------------------------------- -- Helper functions -------------------------------------------------------------------------------- local function makeCategoryLink(cat) if cat then return string.format( '[[%s:%s]]', mw.site.namespaces[14].name, cat ) else return '' end end -- Validation function for the expiry and the protection date local function validateDate(dateString, dateType) lang = lang or mw.language.getContentLanguage() 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) ), 0) end local function makeFullUrl(page, query, display) return string.format( '[%s %s]', tostring(mw.uri.fullUrl(page, query)), display ) end local function toTableEnd(t, pos) -- Sends the value at position pos to the end of array t, and shifts the -- other items down accordingly. return table.insert(t, table.remove(t, pos)) end -------------------------------------------------------------------------------- -- Protection class -------------------------------------------------------------------------------- local Protection = class('Protection') Protection.supportedActions = { edit = true, move = true, autoreview = true } Protection.bannerConfigFields = { 'text', 'explanation', 'tooltip', 'alt', 'link', 'image' } function Protection:initialize(args, cfg, title) self._cfg = cfg self.title = title or mw.title.getCurrentTitle() -- Set action if not args.action then self.action = 'edit' elseif self.supportedActions[args.action] then self.action = args.action else error(string.format( 'invalid action ("%s")', tostring(args.action) ), 0) end -- Set level self.level = effectiveProtectionLevel(self.action, self.title) if self.level == 'accountcreator' then -- Lump titleblacklisted pages in with template-protected pages, -- since templateeditors can do both. self.level = 'templateeditor' elseif not self.level or (self.action == 'move' and self.level == 'autoconfirmed') then -- Users need to be autoconfirmed to move pages anyway, so treat -- semi-move-protected pages as unprotected. self.level = '*' end -- Set expiry if args.expiry then if cfg.indefStrings[args.expiry] then self.expiry = 'indef' elseif type(args.expiry) == 'number' then self.expiry = args.expiry else self.expiry = validateDate(args.expiry, 'expiry date') end end -- Set reason if args[1] then self.reason = mw.ustring.lower(args[1]) if self.reason:find('|') then error('reasons cannot contain the pipe character ("|")', 0) end end -- Set protection date if args.date then self.protectionDate = validateDate(args.date, 'protection date') end -- Set banner config do self.bannerConfig = {} local configTables = {} if cfg.banners[self.action] then configTables[#configTables + 1] = cfg.banners[self.action][self.reason] end if cfg.defaultBanners[self.action] then configTables[#configTables + 1] = cfg.defaultBanners[self.action][self.level] configTables[#configTables + 1] = cfg.defaultBanners[self.action].default end configTables[#configTables + 1] = cfg.masterBanner for i, field in ipairs(self.bannerConfigFields) do for j, t in ipairs(configTables) do if t[field] then self.bannerConfig[field] = t[field] break end end end end end function Protection:isProtected() return self.level ~= '*' end function Protection:makeProtectionCategory() local cfg = self._cfg local title = self.title -- Exit if the page is not protected. if not self:isProtected() then return '' end -- 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 do namespaceFragment = cfg.categoryNamespaceKeys[title.namespace] if not namespaceFragment and title.namespace % 2 == 1 then namespaceFragment = 'talk' end 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. --]] if self.reason and cfg.reasonsWithNamespacePriority[self.reason] then -- table.insert(order, 3, table.remove(order, 2)) toTableEnd(order, 2) else toTableEnd(order, 3) end --[[ -- 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) end end return '' end function Protection:needsExpiry() local cfg = self._cfg return not self.expiry and cfg.expiryCheckActions[self.action] and self.reason -- the old {{pp-protected}} didn't check for expiry and not cfg.reasonsWithoutExpiryCheck[self.reason] end function Protection:isIncorrect() local expiry = self.expiry return not self:isProtected() 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:needsExpiry() then ret[#ret + 1] = makeCategoryLink(msg['tracking-category-expiry']) end if self:isIncorrect() then ret[#ret + 1] = makeCategoryLink(msg['tracking-category-incorrect']) end if self:isTemplateProtectedNonTemplate() then ret[#ret + 1] = makeCategoryLink(msg['tracking-category-template']) end return table.concat(ret) end -------------------------------------------------------------------------------- -- Blurb class -------------------------------------------------------------------------------- local Blurb = class('Blurb') Blurb.bannerTextFields = { text = true, explanation = true, tooltip = true, alt = true, link = true } function Blurb:initialize(protectionObj, args, cfg) self._cfg = cfg self._protectionObj = protectionObj self._args = args 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.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.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 == '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 )) 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 type(self._protectionObj.expiry) == 'number' then return self:_getExpandedMessage('intro-blurb-expiry') else return self:_getExpandedMessage('intro-blurb-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') 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') 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') 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 type(self._protectionObj.expiry) == 'number' then return self:_getExpandedMessage('tooltip-blurb-expiry') else return self:_getExpandedMessage('tooltip-blurb-noexpiry') end end function Blurb:_makeVandalTemplateParameter() return require('Module:Vandal-m')._main{ 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) )) end return self:_substituteParameters(msg) end end -------------------------------------------------------------------------------- -- BannerTemplate class -------------------------------------------------------------------------------- local BannerTemplate = class('BannerTemplate') function BannerTemplate:initialize(protectionObj, cfg) self._cfg = cfg -- Set the image filename. local imageFilename = protectionObj.bannerConfig.image if imageFilename then self._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 expiry = protectionObj.expiry local namespace = protectionObj.title.namespace -- Deal with special cases first. if (namespace == 10 or namespace == 828) and action == 'edit' and level == 'sysop' and (not expiry or expiry == 'indef') then -- Fully protected modules and templates get the special red "indef" -- padlock. self._imageFilename = self._cfg.msg['image-filename-indef'] else -- Deal with regular protection types. local images = self._cfg.images if images[action] then if images[action][level] then self._imageFilename = images[action][level] elseif images[action].default then self._imageFilename = images[action].default end end end end end function BannerTemplate:setImageWidth(width) self._imageWidth = width end function BannerTemplate:setImageTooltip(tooltip) self._imageCaption = tooltip end function BannerTemplate:renderImage() local filename = self._imageFilename or self._cfg.msg['image-filename-default'] or 'Transparent.gif' return newFileLink(filename) :width(self._imageWidth or 20) :alt(self._imageAlt) :link(self._imageLink) :caption(self._imageCaption) :render() end -------------------------------------------------------------------------------- -- Banner class -------------------------------------------------------------------------------- local Banner = BannerTemplate:subclass('Banner') function Banner:initialize(protectionObj, blurbObj, cfg) BannerTemplate.initialize(self, protectionObj, cfg) -- This doesn't need the blurb. self:setImageWidth(40) self:setImageTooltip(blurbObj:makeBannerText('alt')) -- Large banners use the alt text for the tooltip. self._reasonText = blurbObj:makeBannerText('text') self._explanationText = blurbObj:makeBannerText('explanation') self._page = protectionObj.title.prefixedText -- Only makes a difference in testing. 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') 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 = BannerTemplate:subclass('Padlock') function Padlock:initialize(protectionObj, blurbObj, cfg) BannerTemplate.initialize(self, protectionObj, cfg) -- This doesn't need the blurb. self:setImageWidth(20) self:setImageTooltip(blurbObj:makeBannerText('tooltip')) self._imageAlt = blurbObj:makeBannerText('alt') self._imageLink = blurbObj:makeBannerText('link') self._right = cfg.padlockPositions[protectionObj.action] or cfg.padlockPositions.default or '55px' end function Padlock:__tostring() local root = mw.html.create('div') root :addClass('metadata topicon nopopups') :attr('id', 'protected-icon') :css{display = 'none', right = self._right} :wikitext(self:renderImage()) return tostring(root) 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('Module:Protection banner/config') -- Initialise the protection object and check for errors local protectionObjCreated, protectionObj = pcall( Protection.new, Protection, -- equivalent to Protection:new() args, cfg, title ) if not protectionObjCreated then local errorBlurb = cfg.msg['error-message-blurb'] or 'Error: $1.' local errorText = mw.message.newRawMessage(errorBlurb) :params(protectionObj) -- protectionObj is the error message :plain() return string.format( '<strong class="error">%s</strong>%s', errorText, makeCategoryLink(cfg.msg['tracking-category-incorrect']) ) end -- Initialise the blurb object local blurbObj = Blurb:new(protectionObj, args, cfg) local ret = {} -- Render the banner if protectionObj:isProtected() then ret[#ret + 1] = tostring( (yesno(args.small) and Padlock or Banner) :new(protectionObj, blurbObj, cfg) ) 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) getArgs = getArgs or require('Module:Arguments').getArgs cfg = cfg or require('Module:Protection banner/config') local parentTitle = frame:getParent():getTitle() parentTitle = parentTitle:gsub('/sandbox$', '') local defaultArgs = cfg.wrappers[parentTitle] or {} local args = getArgs(frame, {parentOnly = defaultArgs and true}) for k, v in pairs(defaultArgs) do args[k] = v end return p._main(args, cfg) end return p 편집 요약 가온 위키에서의 모든 기여는 크리에이티브 커먼즈 저작자표시-동일조건변경허락 라이선스로 배포된다는 점을 유의해 주세요(자세한 내용에 대해서는 가온 위키:저작권 문서를 읽어주세요). 만약 여기에 동의하지 않는다면 문서를 저장하지 말아 주세요. 또한, 직접 작성했거나 퍼블릭 도메인과 같은 자유 문서에서 가져왔다는 것을 보증해야 합니다. 저작권이 있는 내용을 허가 없이 저장하지 마세요! 취소 편집 도움말 (새 창에서 열림) 이 문서에서 사용한 틀: 모듈:Protection banner/설명문서 (편집)