Jump to content
 







Main menu
   


Navigation  



Main page
Contents
Current events
Random article
About Wikipedia
Contact us
Donate
 




Contribute  



Help
Learn to edit
Community portal
Recent changes
Upload file
 








Search  

































Create account

Log in
 









Create account
 Log in
 




Pages for logged out editors learn more  



Contributions
Talk
 



















Contents

   



(Top)
 


1 Technical details  





2 Class documentation  



2.1  Category  





2.2  Message  





2.3  ArticleHistory  





2.4  Row  





2.5  Status  





2.6  MultiStatus  





2.7  Notice  





2.8  Action  





2.9  CollapsibleNotice  
















Module:Article history






العربية
Azərbaycanca

فارسی
Bahasa Indonesia
Italiano

Kurdî
ि

مصرى
Bahasa Melayu

Qaraqalpaqsha

سنڌي
کوردی

اردو
Tiếng Vit

 

Edit links
 









Module
Talk
 

















Read
View source
View history
 








Tools
   


Actions  



Read
View source
View history
 




General  



What links here
Related changes
Upload file
Special pages
Permanent link
Page information
Get shortened URL
Download QR code
Wikidata item
 




Print/export  



Download as PDF
Printable version
 
















Appearance
   

 





Permanently protected module

From Wikipedia, the free encyclopedia
 


This module implements Template:Article history (edit | talk | history | links | watch | logs). Please see the template page for documentation of how to use it.

Technical details

This module has a configuration module at Module:Article history/config, which can be used to translate/modify the template for use on other wikis. See the config module for instructions on how to modify it. The configuration for the English Wikipedia is very complicated, but it is also possible to use it much more simply. If you want to use the more advanced features provided by the module, you can consult the class documentation below.

There is also a Category class at Module:Article history/Category, which is used by both the main module and the config module.

Class documentation

This is technical documentation for Lua programmers who are looking to adapt this module for use on different wikis.

Category

The Category class is used to generate all of the module's categories. It is loaded in both Module:Article history and Module:Article history/config. You can create a category with Category.new:

Category.new(cat, sort)

The cat variable is the category text, and sort is its sort key.

Once category objects are created, they have the following properties:

They can be rendered into category links by calling tostring() on them.

Message

The Message mixin contains common message-related methods which are available in all classes apart from the Category class. These methods are:

ArticleHistory

An ArticleHistory object does the main work of the module. It fetches the different Row objects, renders the box, renders the error messages, and renders category links. ArticleHistory objects can use all methods from the Message mixin. They also have the following public properties:

ArticleHistory objects have the following public methods:

Calling tostring() on an ArticleHistory object gives you the HTML table made with ArticleHistory:renderHtml concatenated with the category links made with ArticleHistory:renderCategories.

Row

Row objects can use all methods from the Message mixin. They also have the following public properties:

Row objects have the following public methods:

Status

Status objects generate the row detailing the current status of the article. They inherit all properties and methods from Row objects, including the Message mixin methods. They have the following additional properties:

MultiStatus

MultiStatus objects are variations on Status objects for articles that can have multiple current statuses, e.g. Good Article and Former Featured Article. They can be used interchangeably with Status objects.

Notice

Notice objects generate rows containing notices about the article that aren't part of its current status, e.g. the date the article was featured on the Main Page. They inherit all the properties and methods from Row objects, including the Message mixin methods. They don't have any additional properties or methods.

Action

Action objects generate rows detailing a single action in the history of an article, e.g. a Featured Article candidacy. They inherit all properties and methods from Row objects, including the Message mixin methods. They have the following additional properties:

Action objects have the following additional methods:

CollapsibleNotice

CollapsibleNotice objects generate rows containing notices that go in the collapsible part of the template, underneath the list of actions. They can also include a collapsible text field. This is used for notices that are not important enough to be displayed as a (more prominent) Notice object, e.g. DYK notices. CollapsibleNotice objects inherit all the properties and methods from Row objects, including the Message mixin methods. They don't have any additional properties, but they have the following additional methods:

-------------------------------------------------------------------------------
--                            Article history
--
-- This module allows editors to link to all the significant events in an
-- article's history, such as good article nominations and featured article
-- nominations. It also displays its current status, as well as other
-- information, such as the date it was featured on the main page.
-------------------------------------------------------------------------------

local CONFIG_PAGE = 'Module:Article history/config'
local WRAPPER_TEMPLATE = 'Template:Article history'
local DEBUG_MODE = false -- If true, errors are not caught.

-- Load required modules.
require('strict')
local Category = require('Module:Article history/Category')
local yesno = require('Module:Yesno')
local lang = mw.language.getContentLanguage()

-------------------------------------------------------------------------------
-- Helper functions
-------------------------------------------------------------------------------

local function isPositiveInteger(num)
 return type(num) == 'number'
  and math.floor(num) == num
  and num > 0
  and num < math.huge
end

local function substituteParams(msg, ...)
 return mw.message.newRawMessage(msg, ...):plain()
end

local function makeUrlLink(url, display)
 return string.format('[%s %s]', url, display)
end

local function maybeCallFunc(val, ...)
 -- Checks whether val is a function, and if so calls it with the specified
 -- arguments. Otherwise val is returned as-is.
 if type(val) == 'function' then
  return val(...)
 else
  return val
 end
end

local function renderImage(image, caption, size)
 if caption then
  caption = '|' .. caption
 else
  caption = ''
 end
 return string.format('[[File:%s|%s%s]]', image, size, caption)
end

local function addMixin(class, mixin)
 -- Add a mixin to a class. The functions will be shared across classes, so
 -- don't use it for functions that keep state.
 for name, method in pairs(mixin) do
  class[name] = method
 end
end

-------------------------------------------------------------------------------
-- Message mixin
-- This mixin is used by all classes to add message-related methods.
-------------------------------------------------------------------------------

local Message = {}

function Message:message(key, ...)
 -- This fetches the message from the config with the specified key, and
 -- substitutes parameters $1, $2 etc. with the subsequent values it is
 -- passed.
 local msg = self.cfg.msg[key]
 if select('#', ...) > 0 then
  return substituteParams(msg, ...)
 else
  return msg
 end
end

function Message:raiseError(msg, help)
 -- Raises an error with the specified message and help link. Execution
 -- stops unless the error is caught. This is used for errors where
 -- subsequent processing becomes impossible.
 local errorText
 if help then
  errorText = self:message('error-message-help', msg, help)
 else
  errorText = self:message('error-message-nohelp', msg)
 end
 error(errorText, 0)
end

function Message:addWarning(msg, help)
 -- Adds a warning to the object's warnings table. Execution continues as
 -- normal. This is used for errors that should be fixed but that do not
 -- prevent the module from outputting something useful.
 self.warnings = self.warnings or {}
 local warningText
 if help then
  warningText = self:message('warning-help', msg, help)
 else
  warningText = self:message('warning-nohelp', msg)
 end
 table.insert(self.warnings, warningText)
end

function Message:getWarnings()
 return self.warnings or {}
end

-------------------------------------------------------------------------------
-- Row class
-- This class represents one row in the template.
-------------------------------------------------------------------------------

local Row = {}
Row.__index = Row
addMixin(Row, Message)

function Row.new(data)
 local obj = setmetatable({}, Row)
 obj.cfg = data.cfg
 obj.currentTitle = data.currentTitle
 obj.makeData = data.makeData -- used by Row:getData
 return obj
end

function Row:_cachedTry(cacheKey, errorCacheKey, func)
 -- This method is for use in Row object methods that are called more than
 -- once. The results of such methods should be cached to avoid unnecessary
 -- processing. We also cache any errors found and abort if an error was
 -- raised previously, otherwise error messages could be displayed multiple
 -- times.
 --
 -- We use false as a key to cache nil results, so func cannot return false.
 --
 -- @param cacheKey The key to cache successful results with
 -- @param errorCacheKey The key to cache errors with
 -- @param func an anonymous function that returns the method result
 if self[errorCacheKey] then
  return nil
 end
 local ret = self[cacheKey]
 if ret then
  return ret
 elseif ret == false then
  return nil
 end
 local success
 if DEBUG_MODE then
  success = true
  ret = func()
 else
  success, ret = pcall(func)
 end
 if success then
  if ret then
   self[cacheKey] = ret
   return ret
  else
   self[cacheKey] = false
   return nil
  end
 else
  self[errorCacheKey] = true
  -- We have already formatted the error message, so no need to format it
  -- again.
  error(ret, 0)
 end
end

function Row:getData(articleHistoryObj)
 return self:_cachedTry('_dataCache', '_isDataError', function ()
  return self.makeData(articleHistoryObj)
 end)
end

function Row:setIconValues(icon, caption, size)
 self.icon = icon
 self.iconCaption = caption
 self.iconSize = size
end

function Row:getIcon(articleHistoryObj)
 return maybeCallFunc(self.icon, articleHistoryObj, self)
end

function Row:getIconCaption(articleHistoryObj)
 return maybeCallFunc(self.iconCaption, articleHistoryObj, self)
end

function Row:getIconSize()
 return self.iconSize or self.cfg.defaultIconSize or '30px'
end

function Row:renderIcon(articleHistoryObj)
 local icon = self:getIcon(articleHistoryObj)
 if not icon then
  return nil
 end
 return renderImage(
  icon,
  self:getIconCaption(articleHistoryObj),
  self:getIconSize()
 )
end

function Row:setNoticeBarIconValues(icon, caption, size)
 self.noticeBarIcon = icon
 self.noticeBarIconCaption = caption
 self.noticeBarIconSize = size
end

function Row:getNoticeBarIcon(articleHistoryObj)
 local icon = maybeCallFunc(self.noticeBarIcon, articleHistoryObj, self)
 if icon == true then
  icon = self:getIcon(articleHistoryObj)
  if not icon then
   self:raiseError(
    self:message('row-error-missing-icon'),
    self:message('row-error-missing-icon-help')
   )
  end
 end
 return icon
end

function Row:getNoticeBarIconCaption(articleHistoryObj)
 local caption = maybeCallFunc(
  self.noticeBarIconCaption,
  articleHistoryObj,
  self
 )
 if not caption then
  caption = self:getIconCaption(articleHistoryObj)
 end
 return caption
end

function Row:getNoticeBarIconSize()
 return self.noticeBarIconSize or self.cfg.defaultNoticeBarIconSize or '15px'
end

function Row:exportNoticeBarIcon(articleHistoryObj)
 local icon = self:getNoticeBarIcon(articleHistoryObj)
 if not icon then
  return nil
 end
 return renderImage(
  icon,
  self:getNoticeBarIconCaption(articleHistoryObj),
  self:getNoticeBarIconSize()
 )
end

function Row:setText(text)
 self.text = text
end

function Row:getText(articleHistoryObj)
 return maybeCallFunc(self.text, articleHistoryObj, self)
end

function Row:exportHtml(articleHistoryObj)
 if self._html then
  return self._html
 end
 local text = self:getText(articleHistoryObj)
 if not text then
  return nil
 end
 local html = mw.html.create('tr')
 html
  :tag('td')
   :addClass('mbox-image')
   :wikitext(self:renderIcon(articleHistoryObj))
   :done()
  :tag('td')
   :addClass('mbox-text')
   :wikitext(text)
 self._html = html
 return html
end

function Row:setCategories(val)
 -- Set the categories from the object's config. val can be either an array
 -- of strings or a function returning an array of category objects.
 self.categories = val
end

function Row:getCategories(articleHistoryObj)
 local ret = {}
 if type(self.categories) == 'table' then
  for _, cat in ipairs(self.categories) do
   ret[#ret + 1] = Category.new(cat)
  end
 elseif type(self.categories) == 'function' then
  local t = self.categories(articleHistoryObj, self) or {}
  for _, categoryObj in ipairs(t) do
   ret[#ret + 1] = categoryObj
  end
 end
 return ret
end

-------------------------------------------------------------------------------
-- Status class
-- Status objects deal with possible current statuses of the article.
-------------------------------------------------------------------------------

local Status = setmetatable({}, Row)
Status.__index = Status

function Status.new(data)
 local obj = Row.new(data)
 setmetatable(obj, Status)

 obj.id = data.id
 obj.statusCfg = obj.cfg.statuses[obj.id]
 obj.name = obj.statusCfg.name
 obj:setIconValues(
  obj.statusCfg.icon,
  obj.statusCfg.iconCaption or obj.name,
  data.iconSize
 )
 obj:setNoticeBarIconValues(
  obj.statusCfg.noticeBarIcon,
  obj.statusCfg.noticeBarIconCaption or obj.name,
  obj.statusCfg.noticeBarIconSize
 )
 obj:setText(obj.statusCfg.text)
 obj:setCategories(obj.statusCfg.categories)

 return obj
end

function Status:getIconSize()
 return self.iconSize
  or self.statusCfg.iconSize
  or self.cfg.defaultStatusIconSize
  or '50px'
end

function Status:getText(articleHistoryObj)
 local text = Row.getText(self, articleHistoryObj)
 if text then
  return substituteParams(
   text,
   self.currentTitle.subjectPageTitle.prefixedText,
   self.currentTitle.text
  )
 end
end

-------------------------------------------------------------------------------
-- MultiStatus class
-- For when an article can have multiple distinct statuses, e.g. former
-- featured article status and good article status.
-------------------------------------------------------------------------------

local MultiStatus = setmetatable({}, Row)
MultiStatus.__index = MultiStatus

function MultiStatus.new(data)
 local obj = Row.new(data)
 setmetatable(obj, MultiStatus)

 obj.id = data.id
 obj.statusCfg = obj.cfg.statuses[data.id]
 obj.name = obj.statusCfg.name

 -- Set child status objects
 local function getChildStatusData(data, id, iconSize)
  local ret = {}
  for k, v in pairs(data) do
   ret[k] = v
  end
  ret.id = id
  ret.iconSize = iconSize
  return ret
 end
 obj.statuses = {}
 local defaultIconSize = obj.cfg.defaultMultiStatusIconSize or '30px'
 for _, id in ipairs(obj.statusCfg.statuses) do
  table.insert(obj.statuses, Status.new(getChildStatusData(
   data,
   id,
   obj.cfg.statuses[id].iconMultiSize or defaultIconSize
  )))
 end

 return obj
end

function MultiStatus:exportHtml(articleHistoryObj)
 local ret = mw.html.create()
 for _, obj in ipairs(self.statuses) do
  ret:node(obj:exportHtml(articleHistoryObj))
 end
 return ret
end

function MultiStatus:getCategories(articleHistoryObj)
 local ret = {}
 for _, obj in ipairs(self.statuses) do
  for _, categoryObj in ipairs(obj:getCategories(articleHistoryObj)) do
   ret[#ret + 1] = categoryObj
  end
 end
 return ret
end

function MultiStatus:exportNoticeBarIcon()
 local ret = {}
 for _, obj in ipairs(self.statuses) do
  ret[#ret + 1] = obj:exportNoticeBarIcon()
 end
 return table.concat(ret)
end

function MultiStatus:getWarnings()
 local ret = {}
 for _, obj in ipairs(self.statuses) do
  for _, msg in ipairs(obj:getWarnings()) do
   ret[#ret + 1] = msg
  end
 end
 return ret
end

-------------------------------------------------------------------------------
-- Notice class
-- Notice objects contain notices about an article that aren't part of its
-- current status, e.g. the date an article was featured on the main page.
-------------------------------------------------------------------------------

local Notice = setmetatable({}, Row)
Notice.__index = Notice

function Notice.new(data)
 local obj = Row.new(data)
 setmetatable(obj, Notice)

 obj:setIconValues(
  data.icon,
  data.iconCaption,
  data.iconSize
 )
 obj:setNoticeBarIconValues(
  data.noticeBarIcon,
  data.noticeBarIconCaption,
  data.noticeBarIconSize
 )
 obj:setText(data.text)
 obj:setCategories(data.categories)

 return obj
end

-------------------------------------------------------------------------------
-- Action class
-- Action objects deal with a single action in the history of the article. We
-- use getter methods rather than properties for the name and result, etc., as
-- their processing needs to be delayed until after the status object has been
-- initialised. The status object needs to parse the action objects when it is
-- initialised, and the value of some names, etc., in the action objects depend
-- on the status object, so this is necessary to avoid errors/infinite loops.
-------------------------------------------------------------------------------

local Action = setmetatable({}, Row)
Action.__index = Action

function Action.new(data)
 local obj = Row.new(data)
 setmetatable(obj, Action)

 obj.paramNum = data.paramNum

 -- Set the ID
 do
  if not data.code then
   obj:raiseError(
    obj:message('action-error-no-code', obj:getParameter('code')),
    obj:message('action-error-no-code-help')
   )
  end
  local code = mw.ustring.upper(data.code)
  obj.id = obj.cfg.actions[code] and obj.cfg.actions[code].id
  if not obj.id then
   obj:raiseError(
    obj:message(
     'action-error-invalid-code',
     data.code,
     obj:getParameter('code')
    ),
    obj:message('action-error-invalid-code-help')
   )
  end
 end

 -- Add a shortcut for this action's config.
 obj.actionCfg = obj.cfg.actions[obj.id]

 -- Set the link
 obj.link = data.link or obj.currentTitle.talkPageTitle.prefixedText

 -- Set the result ID
 do
  local resultCode = data.resultCode
   and mw.ustring.lower(data.resultCode)
   or '_BLANK'
  if obj.actionCfg.results[resultCode] then
   obj.resultId = obj.actionCfg.results[resultCode].id
  elseif resultCode == '_BLANK' then
   obj:raiseError(
    obj:message(
     'action-error-blank-result',
     obj.id,
     obj:getParameter('resultCode')
    ),
    obj:message('action-error-blank-result-help')
   )
  else
   obj:raiseError(
    obj:message(
     'action-error-invalid-result',
     data.resultCode,
     obj.id,
     obj:getParameter('resultCode')
    ),
    obj:message('action-error-invalid-result-help')
   )
  end
 end

 -- Set the date
 if data.date then
  local success, date = pcall(
   lang.formatDate,
   lang,
   obj:message('action-date-format'),
   data.date
  )
  if success and date then
   obj.date = date
  else
   obj:addWarning(
    obj:message(
     'action-warning-invalid-date',
     data.date,
     obj:getParameter('date')
    ),
    obj:message('action-warning-invalid-date-help')
   )
  end
 else
  obj:addWarning(
   obj:message(
    'action-warning-no-date',
    obj.paramNum,
    obj:getParameter('date'),
    obj:getParameter('code')
   ),
   obj:message('action-warning-no-date-help')
  )
 end
 obj.date = obj.date or obj:message('action-date-missing')

 -- Set the oldid
 obj.oldid = tonumber(data.oldid)
 if data.oldid and (not obj.oldid or not isPositiveInteger(obj.oldid)) then
  obj.oldid = nil
  obj:addWarning(
   obj:message(
    'action-warning-invalid-oldid',
    data.oldid,
    obj:getParameter('oldid')
   ),
   obj:message('action-warning-invalid-oldid-help')
  )
 end

 -- Set the notice bar icon values
 obj:setNoticeBarIconValues(
  data.noticeBarIcon,
  data.noticeBarIconCaption,
  data.noticeBarIconSize
 )

 -- Set the categories
 obj:setCategories(obj.actionCfg.categories)

 return obj
end

function Action:getParameter(key)
 -- Finds the original parameter name for the given key that was passed to
 -- Action.new.
 local prefix = self.cfg.actionParamPrefix
 local suffix
 for k, v in pairs(self.cfg.actionParamSuffixes) do
  if v == key then
   suffix = k
   break
  end
 end
 if not suffix then
  error('invalid key "' .. tostring(key) .. '" passed to Action:getParameter', 2)
 end
 return prefix .. tostring(self.paramNum) .. suffix
end

function Action:getName(articleHistoryObj)
 return maybeCallFunc(self.actionCfg.name, articleHistoryObj, self)
end

function Action:getResult(articleHistoryObj)
 return maybeCallFunc(
  self.actionCfg.results[self.resultId].text,
  articleHistoryObj,
  self
 )
end

function Action:exportHtml(articleHistoryObj)
 if self._html then
  return self._html
 end

 local row = mw.html.create('tr')

 -- Date cell
 local dateCell = row:tag('td')
 if self.oldid then
  dateCell
   :tag('span')
    :addClass('plainlinks')
    :wikitext(makeUrlLink(
     self.currentTitle.subjectPageTitle:fullUrl{oldid = self.oldid},
     self.date
    ))
 else
  dateCell:wikitext(self.date)
 end

 -- Process cell
 row
  :tag('td')
   :wikitext(string.format(
    "'''[[%s|%s]]'''",
    self.link,
    self:getName(articleHistoryObj)
   ))

 -- Result cell
 row
  :tag('td')
   :wikitext(self:getResult(articleHistoryObj))

 self._html = row
 return row
end

-------------------------------------------------------------------------------
-- CollapsibleNotice class
-- This class makes notices that go in the collapsible part of the template,
-- underneath the list of actions.
-------------------------------------------------------------------------------

local CollapsibleNotice = setmetatable({}, Row)
CollapsibleNotice.__index = CollapsibleNotice

function CollapsibleNotice.new(data)
 local obj = Row.new(data)
 setmetatable(obj, CollapsibleNotice)

 obj:setIconValues(
  data.icon,
  data.iconCaption,
  data.iconSize
 )
 obj:setNoticeBarIconValues(
  data.noticeBarIcon,
  data.noticeBarIconCaption,
  data.noticeBarIconSize
 )
 obj:setText(data.text)
 obj:setCollapsibleText(data.collapsibleText)
 obj:setCategories(data.categories)

 return obj
end

function CollapsibleNotice:setCollapsibleText(s)
 self.collapsibleText = s
end

function CollapsibleNotice:getCollapsibleText(articleHistoryObj)
 return maybeCallFunc(self.collapsibleText, articleHistoryObj, self)
end

function CollapsibleNotice:getIconSize()
 return self.iconSize
  or self.cfg.defaultCollapsibleNoticeIconSize
  or '20px'
end

function CollapsibleNotice:exportHtml(articleHistoryObj, isInCollapsibleTable)
 local cacheKey = isInCollapsibleTable
  and '_htmlCacheCollapsible'
  or '_htmlCacheDefault'
 return self:_cachedTry(cacheKey, '_isHtmlError', function ()
  local text = self:getText(articleHistoryObj)
  if not text then
   return nil
  end

  local function maybeMakeCollapsibleTable(cell, text, collapsibleText)
   -- If collapsible text is specified, makes a collapsible table
   -- inside the cell with two rows, a header row with one cell and a
   -- collapsed row with one cell. These are filled with text and
   -- collapsedText, respectively. If no collapsible text is
   -- specified, the text is added to the cell as-is.
   if collapsibleText then
    cell
     :tag('div')
      :addClass('mw-collapsible mw-collapsed')
      :tag('div')
       :wikitext(text)
       :done()
      :tag('div')
       :addClass('mw-collapsible-content')
       :css('border', '1px silver solid')
       :wikitext(collapsibleText)
   else
    cell:wikitext(text)
   end
  end

  local html = mw.html.create('tr')
  local icon = self:renderIcon(articleHistoryObj)
  local collapsibleText = self:getCollapsibleText(articleHistoryObj)
  if isInCollapsibleTable then
   local textCell = html:tag('td')
    :attr('colspan', 3)
    :css('width', '100%')
   local rowText
   if icon then
    rowText = icon .. ' ' .. text
   else
    rowText = text
   end
   maybeMakeCollapsibleTable(textCell, rowText, collapsibleText)
  else
   local textCell = html
    :tag('td')
     :addClass('mbox-image')
     :wikitext(icon)
     :done()
    :tag('td')
     :addClass('mbox-text')
   maybeMakeCollapsibleTable(textCell, text, collapsibleText)
  end

  return html
 end)
end

-------------------------------------------------------------------------------
-- ArticleHistory class
-- This class represents the whole template.
-------------------------------------------------------------------------------

local ArticleHistory = {}
ArticleHistory.__index = ArticleHistory
addMixin(ArticleHistory, Message)

function ArticleHistory.new(args, cfg, currentTitle)
 local obj = setmetatable({}, ArticleHistory)

 -- Set input
 obj.args = args or {}
 obj.currentTitle = currentTitle or mw.title.getCurrentTitle()

 -- Define object structure.
 obj._errors = {}
 obj._allObjectsCache = {}

 -- Format the config
 local function substituteAliases(t, ret)
  -- This function substitutes strings found in an "aliases" subtable
  -- as keys in the parent table. It works recursively, so "aliases"
  -- subtables can be placed at any level. It assumes that tables will
  -- not be nested recursively, which should be true in the case of our
  -- config file.
  ret = ret or {}
  for k, v in pairs(t) do
   if k ~= 'aliases' then
    if type(v) == 'table' then
     local newRet = {}
     ret[k] = newRet
     if v.aliases then
      for _, alias in ipairs(v.aliases) do
       ret[alias] = newRet
      end
     end
     substituteAliases(v, newRet)
    else
     ret[k] = v
    end
   end
  end
  return ret
 end
 obj.cfg = substituteAliases(cfg or require(CONFIG_PAGE))

 --[[
 -- Get a table of the arguments sorted by prefix and number. Non-string
 -- keys and keys that don't contain a number are ignored. (This means that
 -- positional parameters are ignored, as they are numbers, not strings.)
 -- The parameter numbers are stored in the first positional parameter of
 -- the subtables, and any gaps are removed so that the tables can be
 -- iterated over with ipairs.
 --
 -- For example, these arguments:
 --   {a1x = 'eggs', a1y = 'spam', a2x = 'chips', b1z = 'beans', b3x = 'bacon'}
 -- would translate into this prefixArgs table.
 --   {
 --     a = {
 --       {1, x = 'eggs', y = 'spam'},
 --       {2, x = 'chips'}
 --     },
 --     b = {
 --       {1, z = 'beans'},
 --       {3, x = 'bacon'}
 --     }
 --   }
 --]]
 do
  local prefixArgs = {}
  for k, v in pairs(obj.args) do
   if type(k) == 'string' then
    local prefix, num, suffix = k:match('^(.-)([1-9][0-9]*)(.*)$')
    if prefix then
     num = tonumber(num)
     prefixArgs[prefix] = prefixArgs[prefix] or {}
     prefixArgs[prefix][num] = prefixArgs[prefix][num] or {}
     prefixArgs[prefix][num][suffix] = v
     prefixArgs[prefix][num][1] = num
    end
   end
  end
  -- Remove the gaps
  local prefixArrays = {}
  for prefix, prefixTable in pairs(prefixArgs) do
   prefixArrays[prefix] = {}
   local numKeys = {}
   for num in pairs(prefixTable) do
    numKeys[#numKeys + 1] = num
   end
   table.sort(numKeys)
   for _, num in ipairs(numKeys) do
    table.insert(prefixArrays[prefix], prefixTable[num])
   end
  end
  obj.prefixArgs = prefixArrays
 end

 return obj
end

function ArticleHistory:try(func, ...)
 if DEBUG_MODE then
  local val = func(...)
  return val
 else
  local success, val = pcall(func, ...)
  if success then
   return val
  else
   table.insert(self._errors, val)
   return nil
  end
 end
end

function ArticleHistory:getActionObjects()
 -- Gets an array of action objects for the parameters specified by the
 -- user. We memoise this so that the parameters only have to be processed
 -- once.
 if self.actions then
  return self.actions
 end

 -- Get the action args, and exit if they don't exist.
 local actionArgs = self.prefixArgs[self.cfg.actionParamPrefix]
 if not actionArgs then
  self.actions = {}
  return self.actions
 end

 -- Make the objects.
 local actions = {}
 local suffixes = self.cfg.actionParamSuffixes
 for _, t in ipairs(actionArgs) do
  local objArgs = {}
  for k, v in pairs(t) do
   local newK = suffixes[k]
   if newK then
    objArgs[newK] = v
   end
  end
  objArgs.paramNum = t[1]
  objArgs.cfg = self.cfg
  objArgs.currentTitle = self.currentTitle
  local actionObj = self:try(Action.new, objArgs)
  table.insert(actions, actionObj)
 end
 self.actions = actions
 return actions
end

function ArticleHistory:getStatusIdForCode(code)
 -- Gets a status ID given a status code. If no code is specified, returns
 -- nil, and if the code is invalid, raises an error.
 if not code then
  return nil
 end
 local statuses = self.cfg.statuses
 local codeUpper = mw.ustring.upper(code)
 if statuses[codeUpper] then
  return statuses[codeUpper].id
 else
  self:addWarning(
   self:message('articlehistory-warning-invalid-status', code),
   self:message('articlehistory-warning-invalid-status-help')
  )
  return nil
 end
end

function ArticleHistory:getStatusObj()
 -- Get the status object for the current status.
 if self.statusObj == false then
  return nil
 elseif self.statusObj ~= nil then
  return self.statusObj
 end
 local statusId
 if self.cfg.getStatusIdFunction then
  statusId = self:try(self.cfg.getStatusIdFunction, self)
 else
  statusId = self:try(
   self.getStatusIdForCode, self,
   self.args[self.cfg.currentStatusParam]
  )
 end
 if not statusId then
  self.statusObj = false
  return nil
 end

 -- Check that some actions were specified, and if not add a warning.
 local actions = self:getActionObjects()
 if #actions < 1 then
  self:addWarning(
   self:message('articlehistory-warning-status-no-actions'),
   self:message('articlehistory-warning-status-no-actions-help')
  )
 end

 -- Make a new status object.
 local statusObjData = {
  id = statusId,
  currentTitle = self.currentTitle,
  cfg = self.cfg
 }
 local isMulti = self.cfg.statuses[statusId].isMulti
 local initFunc = isMulti and MultiStatus.new or Status.new
 local statusObj = self:try(initFunc, statusObjData)
 self.statusObj = statusObj or false
 return self.statusObj or nil
end

function ArticleHistory:getStatusId()
 local statusObj = self:getStatusObj()
 return statusObj and statusObj.id
end

function ArticleHistory:_noticeFactory(memoizeKey, configKey, class)
 -- This holds the logic for fetching tables of Notice and CollapsibleNotice
 -- objects.
 if self[memoizeKey] then
  return self[memoizeKey]
 end
 local ret = {}
 for _, t in ipairs(self.cfg[configKey] or {}) do
  if t.isActive(self) then
   local data = {}
   for k, v in pairs(t) do
    if k ~= 'isActive' then
     data[k] = v
    end
   end
   data.cfg = self.cfg
   data.currentTitle = self.currentTitle
   ret[#ret + 1] = class.new(data)
  end
 end
 self[memoizeKey] = ret
 return ret
end

function ArticleHistory:getNoticeObjects()
 return self:_noticeFactory('notices', 'notices', Notice)
end

function ArticleHistory:getCollapsibleNoticeObjects()
 return self:_noticeFactory(
  'collapsibleNotices',
  'collapsibleNotices',
  CollapsibleNotice
 )
end

function ArticleHistory:getAllObjects(addSelf)
 local cacheKey = addSelf and 'addSelf' or 'default'
 local ret = self._allObjectsCache[cacheKey]
 if not ret then
  ret = {}
  local statusObj = self:getStatusObj()
  if statusObj then
   ret[#ret + 1] = statusObj
  end
  local objTables = {
   self:getNoticeObjects(),
   self:getActionObjects(),
   self:getCollapsibleNoticeObjects()
  }
  for _, t in ipairs(objTables) do
   for _, obj in ipairs(t) do
    ret[#ret + 1] = obj
   end
  end
  if addSelf then
   ret[#ret + 1] = self
  end
  self._allObjectsCache[cacheKey] = ret
 end
 return ret
end

function ArticleHistory:getNoticeBarIcons()
 local ret = {}
 -- Icons that aren't part of a row.
 if self.cfg.noticeBarIcons then
  for _, data in ipairs(self.cfg.noticeBarIcons) do
   if data.isActive(self) then
    ret[#ret + 1] = renderImage(
     data.icon,
     nil,
     data.size or self.cfg.defaultNoticeBarIconSize
    )
   end
  end
 end
 -- Icons in row objects.
 for _, obj in ipairs(self:getAllObjects()) do
  ret[#ret + 1] = obj:exportNoticeBarIcon(self)
 end
 return ret
end

function ArticleHistory:getErrorMessages()
 -- Returns an array of error/warning strings. Error strings come first.
 local ret = {}
 for _, msg in ipairs(self._errors) do
  ret[#ret + 1] = msg
 end
 for _, obj in ipairs(self:getAllObjects(true)) do
  for _, msg in ipairs(obj:getWarnings()) do
   ret[#ret + 1] = msg
  end
 end
 return ret
end

function ArticleHistory:categoriesAreActive()
 -- Returns a boolean indicating whether categories should be output or not.
 local title = self.currentTitle
 local ns = title.namespace
 return title.isTalkPage
  and ns ~= 3 -- not user talk
  and ns ~= 119 -- not draft talk
end

function ArticleHistory:renderCategories()
 local ret = {}

 if self:categoriesAreActive() then
  -- Child object categories
  for _, obj in ipairs(self:getAllObjects()) do
   local categories = self:try(obj.getCategories, obj, self)
   for _, categoryObj in ipairs(categories or {}) do
    ret[#ret + 1] = tostring(categoryObj)
   end
  end

  -- Extra categories
  for _, func in ipairs(self.cfg.extraCategories or {}) do
   local cats = func(self) or {}
   for _, categoryObj in ipairs(cats) do
    ret[#ret + 1] = tostring(categoryObj)
   end
  end
 end

 return table.concat(ret)
end

function ArticleHistory:__tostring()
 local root = mw.html.create()

 -- Table root
 local tableRoot = root:tag('table')
 tableRoot:addClass('article-history tmbox tmbox-notice')

 -- Status
 local statusObj = self:getStatusObj()
 if statusObj then
  tableRoot:node(self:try(statusObj.exportHtml, statusObj, self))
 end

 -- Notices
 local notices = self:getNoticeObjects()
 for _, noticeObj in ipairs(notices) do
  tableRoot:node(self:try(noticeObj.exportHtml, noticeObj, self))
 end

 -- Get action objects and the collapsible notice objects, and generate the
 -- HTML objects for the action objects. We need the action HTML objects so
 -- that we can accurately calculate the number of collapsible rows, as some
 -- action objects may generate errors when the HTML is generated.
 local actions = self:getActionObjects() or {}
 local collapsibleNotices = self:getCollapsibleNoticeObjects() or {}
 local collapsibleNoticeHtmlObjects, actionHtmlObjects = {}, {}
 for _, obj in ipairs(actions) do
  table.insert(
   actionHtmlObjects,
   self:try(obj.exportHtml, obj, self)
  )
 end
 for _, obj in ipairs(collapsibleNotices) do
  table.insert(
   collapsibleNoticeHtmlObjects,
   self:try(obj.exportHtml, obj, self, true) -- Render the collapsed version
  )
 end
 local nActionRows = #actionHtmlObjects
 local nCollapsibleRows = nActionRows + #collapsibleNoticeHtmlObjects

 -- Find out if we are collapsed or not.
 local isCollapsed = yesno(self.args.collapse)
 if isCollapsed == nil then
  if self.cfg.uncollapsedRows == 'all' then
   isCollapsed = false
  elseif nCollapsibleRows == 1 then
   isCollapsed = false
  else
   isCollapsed = nCollapsibleRows > (tonumber(self.cfg.uncollapsedRows) or 3)
  end
 end

 -- If we are not collapsed, re-render the collapsible notices in the
 -- non-collapsed version.
 if not isCollapsed then
  collapsibleNoticeHtmlObjects = {}
  for _, obj in ipairs(collapsibleNotices) do
   table.insert(
    collapsibleNoticeHtmlObjects,
    self:try(obj.exportHtml, obj, self, false)
   )
  end
 end

 -- Collapsible table for actions and collapsible notices. Collapsible
 -- notices are only included in the table if it is collapsed. Action rows
 -- are always included.
 local collapsibleTable
 if isCollapsed or nActionRows > 0 then
  -- Collapsible table base
  collapsibleTable = tableRoot
   :tag('tr')
    :tag('td')
     :attr('colspan', 2)
     :css('width', '100%')
     :tag('table')
      :addClass('article-history-milestones')
      :addClass(isCollapsed and 'mw-collapsible mw-collapsed' or nil)
      :css('width', '100%')
      :css('background', 'transparent')
      :css('font-size', '90%')

  -- Header row
  local ctHeader = collapsibleTable
   :tag('tr')
    :tag('th')
     :attr('colspan', 3)
     :css('font-size', '110%')

  -- Notice bar
  if isCollapsed then
   local noticeBarIcons = self:getNoticeBarIcons()
   if #noticeBarIcons > 0 then
    local noticeBar = ctHeader:tag('span'):css('float', 'left')
    for _, icon in ipairs(noticeBarIcons) do
     noticeBar:wikitext(icon)
    end
    ctHeader:wikitext(' ')
   end
  end

  -- Header text
  if mw.site.namespaces[self.currentTitle.namespace].subject.id == 0 then
   ctHeader:wikitext(self:message('milestones-header'))
  else
   ctHeader:wikitext(self:message(
    'milestones-header-other-ns',
    self.currentTitle.subjectNsText
   ))
  end

  -- Subheadings
  if nActionRows > 0 then
   collapsibleTable
    :tag('tr')
     :css('text-align', 'left')
     :tag('th')
      :wikitext(self:message('milestones-date-header'))
      :done()
     :tag('th')
      :wikitext(self:message('milestones-process-header'))
      :done()
     :tag('th')
      :wikitext(self:message('milestones-result-header'))
  end

  -- Actions
  for _, htmlObj in ipairs(actionHtmlObjects) do
   collapsibleTable:node(htmlObj)
  end
 end

 -- Collapsible notices and current status
 -- These are only included in the collapsible table if it is collapsed.
 -- Otherwise, they are added afterwards, so that they align with the
 -- notices.
 do
  local tableNode, statusColspan
  if isCollapsed then
   tableNode = collapsibleTable
   statusColspan = 3
  else
   tableNode = tableRoot
   statusColspan = 2
  end

  -- Collapsible notices
  for _, obj in ipairs(collapsibleNotices) do
   tableNode:node(self:try(obj.exportHtml, obj, self, isCollapsed))
  end

  -- Current status
  if statusObj and nActionRows > 1 then
   tableNode
    :tag('tr')
     :tag('td')
      :attr('colspan', statusColspan)
      :wikitext(self:message('status-blurb', statusObj.name))
  end
 end

 -- Get the categories. We have to do this before the error row, so that
 -- category errors display.
 local categories = self:renderCategories()

 -- Error row and error category
 local errors = self:getErrorMessages()
 local errorCategory
 if #errors > 0 then
  local errorList = tableRoot
   :tag('tr')
    :tag('td')
     :attr('colspan', 2)
     :addClass('mbox-text')
     :tag('ul')
      :addClass('error')
      :css('font-weight', 'bold')
  for _, msg in ipairs(errors) do
   errorList:tag('li'):wikitext(msg)
  end
  if self:categoriesAreActive() then
   errorCategory = tostring(Category.new(self:message(
    'error-category'
   )))
  end

 -- If there are no errors and no active objects, then exit. We can't make
 -- this check earlier as we don't know where the errors may be until we
 -- have finished rendering the banner.
 elseif #self:getAllObjects() < 1 then
  return ''
 end

 -- Add the categories
 root:wikitext(categories)
 root:wikitext(errorCategory)
 
 local frame = mw.getCurrentFrame()
 return frame:extensionTag{
  name = 'templatestyles', args = { src = 'Module:Message box/tmbox.css' }
 } .. frame:extensionTag{
  name = 'templatestyles', args = { src = 'Module:Article history/styles.css' }
 } .. tostring(root)
end

-------------------------------------------------------------------------------
-- Exports
-- These functions are called from Lua and from wikitext.
-------------------------------------------------------------------------------

local p = {}

function p._main(args, cfg, currentTitle)
 local articleHistoryObj = ArticleHistory.new(args, cfg, currentTitle)
 return tostring(articleHistoryObj)
end

function p.main(frame)
 local args = require('Module:Arguments').getArgs(frame, {
  wrappers = WRAPPER_TEMPLATE
 })
 if frame:getTitle():find('sandbox', 1, true) then
  CONFIG_PAGE = CONFIG_PAGE .. '/sandbox'
 end
 return p._main(args)
end

function p._exportClasses()
 return {
  Message = Message,
  Row = Row,
  Status = Status,
  MultiStatus = MultiStatus,
  Notice = Notice,
  Action = Action,
  CollapsibleNotice = CollapsibleNotice,
  ArticleHistory = ArticleHistory
 }
end

return p

Retrieved from "https://en.wikipedia.org/w/index.php?title=Module:Article_history&oldid=1117470066"

Categories: 
Modules subject to page protection
Templates using TemplateStyles
Hidden category: 
Wikipedia template-protected modules
 



This page was last edited on 21 October 2022, at 22:05 (UTC).

Text is available under the Creative Commons Attribution-ShareAlike License 4.0; additional terms may apply. By using this site, you agree to the Terms of Use and Privacy Policy. Wikipedia® is a registered trademark of the Wikimedia Foundation, Inc., a non-profit organization.



Privacy policy

About Wikipedia

Disclaimers

Contact Wikipedia

Code of Conduct

Developers

Statistics

Cookie statement

Mobile view



Wikimedia Foundation
Powered by MediaWiki