Module:Header

From Suresh Joshi

Documentation for this module may be created at Module:Header/doc

--[=[
This is a module to implement logic for the {{header}} template

It doesn't do everything yet, but over time it can accrete functions from
the template, which will become simpler and simpler until it's just an invoke.

Not implemented yet:
 * Categories (deprecated anyway)
 * Subpage checking
]=]

local p = {} --p stands for package

local yesno = require('Module:Yesno')
local getArgs = require('Module:Arguments').getArgs


-- get the param_override or param parameter in that order
-- nil if neither is there
function get_arg_or_override(args, param)
	if args["override_" .. param] then
		return args["override_" .. param]
	elseif args[param] then
		return args[param]
	end
	
	return nil
end

-- return true if any value in list is nil in args
-- (nil means not present; empty string is not nil)
function any_arg_nil(args, list)
	for k,v in pairs(list) do
		if args[v] == nil then
			return true
		end
	end
	return false
end

-- return true if any value in list is not nil or empty in args
-- (nil means not present; empty string is not nil)
function has_any_arg(args, list)
	for k,v in pairs(list) do
		if args[v] ~= nil and args[v] ~= "" then
			return true
		end
	end
	return false
end

--[=[
Construct a warning if required parameters are missing
]=]
function p.missing_params_error(frame)
	local args = getArgs(frame, {
		removeBlanks = false
	})
	
	local s = ""
	
	local required_args = {'title', 'section', 'author', 'previous', 'next', 'notes'}
	if any_arg_nil(args, required_args) then
		local error_div = mw.html.create("div")
			:css({['margin-right'] = 'auto',
				['margin-left'] = 'auto',
				['border-top'] = '1px solid #CCC',
				['border-right']  = '1px solid #CCC',
				['border-bottom'] = '1px hidden transparent',
				['border-left'] = '1px solid #CCC',
				['text-align'] = 'center'
			})
			
		error_div:tag("span")
			:addClass("error")
			:css({
				['font-size'] = "90%",
				['font-weight'] = "bold",
			})
			:wikitext("Template error: please do not remove empty parameters (see the [[WS:STYLE#Templates|style guide]] and [[Template:Header#documentation|template documentation]]).")

		s = tostring(error_div)
		
		-- categorise only in mainspace
		if mw.title.getCurrentTitle().namespace == 0 then
			s = s .. "[[Category:Headers missing parameters]]"
		end
	end
	return s
end

-- Place each value from a table of IDs:values into the given parent element
function append_mf_values(parent, values)
	for k,v in pairs(values) do
		parent:tag("span")
			:attr("id", k)
			:wikitext(v)
	end
end

-- Create the Microformat wrapper div
function construct_mf_wrapper(args)
	local mf_div =  mw.html.create("div")
	mf_div:addClass("ws-noexport")
	    :attr("id", "ws-data")
		:css({
			speak = "none"
		})
	
	-- hide the microformat unless it's overriden
	if not (args["show_microformat"] and yesno(args['show_microformat'])) then
		mf_div:css("display", "none")
	end
	return mf_div
end

-- Collect all the values of microformat data from the arguments provided
-- Returns a table of microformat ID:contents.
function collect_mf_data(args)
	-- collect the MF values here
	local mf = {};

	mf["ws-article-id"] = mw.title.getCurrentTitle().id 
	
	-- add the title
	if args["title"] then
		mf["ws-title"] = args['title']

		-- append section if there is one
		if args["section"] then
			mf["ws-title"] = mf["ws-title"] .. " — " .. args["section"]
		end
	end
	
	local author = get_arg_or_override(args, "contributor")
	if not author then
		author = get_arg_or_override(args, "section_author")	
	end
	if not author then
		author = get_arg_or_override(args, "author")	
	end
	if author then
		mf['ws-author'] = author
	end
	
	local translator = get_arg_or_override(args, "translator")
	if translator then
		mf['ws-translator'] = translator
	end
	
	local year = get_arg_or_override(args, "year")
	if year then
		mf['ws-year'] = year
	end
	
	if args['cover'] then
		mf['ws-cover'] = args['cover']	
	end
	
	return mf
end

--[=[
Construct the [[Help:Microformat]] for the page.

This is in the form:
<div id="ws-data" ...>
  <span id="ws-title">Title here...</span>
  ...
<div>
]=]
function p.microformat(frame)
	local args = getArgs(frame)

	local mf_div =  construct_mf_wrapper(args)
	local mf = collect_mf_data(args)

	append_mf_values(mf_div, mf)
	return tostring(mf_div)
end

function make_category_list(categories)
	local s = ""
	for k,v in pairs(categories) do
		s = s .. "[[Category:" .. v .. "]]\n"
	end
	return s
end

--[=[
Detect illegal formatting in fields like "section" and "title"
]=]
function illegal_formatting(str)
	return string.match(str, "'''?")
		or string.match(str, "<%s*/?%s*[iIbB]%s*>")
		-- add more cases here
end

function check_non_existent_author_pages(args, param, categories)

	if args[param] then
		-- some pages expect an invalid author
		local special = false
		local lower_arg =  string.lower(args[param])
		if lower_arg == "unknown" or lower_arg == "not mentioned" then
			special = true
		end
		
		if not special then
			local target = mw.title.makeTitle("Author", args[param])
			-- expensive function!
			if not target.exists then
				table.insert(categories, "Works with non-existent author pages")
			end
		end
	end
end

--[=[
Construct the automatic categories for the header
]=]
function p.categories(frame)
	local args = getArgs(frame)
	
	local categories = {}
	local this_page = mw.title.getCurrentTitle();

	if args["override_author"] then
		table.insert(categories, "Pages with override author")
	end
	
	if this_page:inNamespaces(0, 114) then
		check_non_existent_author_pages(args, "author", categories)
		check_non_existent_author_pages(args, "editor", categories)
		check_non_existent_author_pages(args, "translator", categories)
		check_non_existent_author_pages(args, "section_translator", categories)
		check_non_existent_author_pages(args, "section_author", categories)
		check_non_existent_author_pages(args, "contributor", categories)
	end
	
	if args["contributor"] or args["section_author"] then
		table.insert(categories, "Pages with contributor")
	end
	
	if args["override_contributor"] or args["override_section_author"] then
		table.insert(categories, "Pages with override contributor")
	end
	
	local author = get_arg_or_override(args, "author")
	if author and (string.lower(author) == "unknown") then
		table.insert(categories, "Anonymous texts")
	end
	
	local editor = get_arg_or_override(args, "editor")
	if editor then
		editor = string.lower(editor)
		if editor == "unknown" or editor == "?" then
			table.insert(categories, "Works with unknown editors")
		elseif editor == "not mentioned" then
			table.insert(categories, "Works with unmentioned editors")
		end
	end
	
	local translator = get_arg_or_override(args, "translator")
	if translator then
		translator = string.lower(translator)
		if translator == "unknown" or translator == "not mentioned" or translator == "?" then
			table.insert(categories, "Translations without translator information specified")
		elseif translator == "wikisource" and (this_page.baseText == this_page.text) then
			-- if a basepage
			-- ?? why is this not done by {{translation}} ??
			table.insert(categories, "Wikisource translations")
		end
	end

	if args["shortcut"] then
		table.insert(categories, "Mainspace pages with shortcuts")
	end
	
	if args["override_year"] then
		table.insert(categories, "pages with override year")
	end
	if args["noyear"] then
		table.insert(categories, "pages with noyear")
	end
	if args["noyearcat"] then
		table.insert(categories, "pages with noyearcat")
	end
	
	if args["cover"] then
		table.insert(categories, "Pages with an export cover")
	end
	
	-- sanity/maintenance checks on various parameters
	
	-- allow_illegal_formatting parameter suppresses this check
	-- used by, for example, [[Template:Versions]]
	if args["allow_illegal_formatting"] == nil then

		if args["title"] and illegal_formatting(args['title']) then
			table.insert(categories, "pages with illegal formatting in header fields")
		end
		
		if args["section"] and illegal_formatting(args['section']) then
			table.insert(categories, "pages with illegal formatting in header fields")
		end
	end

	return make_category_list(categories)
end

function get_plain_sister(frame, args, sister_args)
	local ps_args = {}
	for k,v in pairs(sister_args) do
		if args[v] then
			ps_args[v] = args[v]
		end
	end
	
	return frame:expandTemplate{title = "plain sister", args = ps_args}
end

--[=[
Return the title span

> 
--]=]
function construct_title(args)
	local title = mw.html.create("span")
		:attr("id", "header_title_text")
		:css({
			["font-weight"] = "bold",
		})
		
	if args["title"] then
		title:wikitext(args["title"])
	else
		title:wikitext("Untitled")
	end
	return title
end

--[=[
Construct the title field
]=]
function p.title(frame)
	local args = getArgs(frame)

	local title = construct_title(args)
	return tostring(title)
end

--[=[
Construct the year span
--]=]
function construct_year(frame, args)

	local year = mw.html.create("span")
		:attr("id", "header_year_text")
		
	if args["year_override"] then
		year:wikitext(args["year_override"])
	else
		local year_args = {
			[1] = args["year"],
			noprint = "0",
			nocat = "0",
		}
		
		if args["noyear"] then
			year_args['noprint'] = "1"
		end
		
		if args["disambiguation"] and yesno(args['disambiguation']) then
			-- disambiguations never categorise
			year_args['nocat'] = "1"
		else
			if args["noyearcat"] and yesno(args['noyearcat']) then
				-- manually no-catted
				year_args['nocat'] = "1"
			elseif mw.title.getCurrentTitle().isSubpage then
				-- only categorise if this is a base page
				year_args['nocat'] = "1"
			end
		end
		mw.logObject(year_args)
		year:wikitext(mw.text.trim(frame:expandTemplate{
			title = "header/year",
			args  = year_args
		}))
	end
	return year
end

--[=[
Construct the year field
]=]	
function p.year(frame)
	local args = getArgs(frame)

	ret = ""
	if get_arg_or_override(args, "year") then
		ret = tostring(construct_year(frame, args))
	end

	return ret
end

function create_vcard(id, content, wrap_fn)
	local span = mw.html.create("span")
		:addClass("vcard")
		:css({
			['font-style'] = 'italic'
		})
		:attr("id", id)
	
	if wrap_fn then
		span:tag("span")
			:addClass("fn")
			:wikitext(content)
	else
		span:wikitext(content)
	end
	
	return span
end

function p.author(frame)
	local args = getArgs(frame)
	
	local param_name = "author"
	local prefix = "by"
	local id = "header_author_text"

	local s = ""
	local atext;
	
	local wrap_fn = true

	if args["override_" .. param_name] then
		s = s .. "<br/>"
		atext = args["override_" .. param_name]
	elseif args[param_name] then
		if args['section'] then
			s = s .. "&#32;"
		else
			s = s .. "<br/>"
		end
		
		if string.lower(args[param_name]) == "unknown" then
			atext = "Unknown"
			wrap_fn = false
		else
			atext = "[[Author:" .. args[param_name] .. "|" .. args[param_name] .. "]]"
		end
 		
		s = s .. "<i>" .. prefix .. "</i>" .. " "
	end
	
	local a_span = create_vcard(id, atext, wrap_fn)
	return s .. tostring(a_span)
end


function p.editor(frame)
	local args = getArgs(frame)

	local ed = get_arg_or_override(args, "editor")

	-- no editors
	if ed == nil then
		return ""
	end
	
	local have_authors = get_arg_or_override(args, "author") ~= nil

	local s = " "
	if have_authors then
		s = ", "
	end
	
	if not have_authors and not args["section"] then
		s = s .. "<br/>"
	end
	
	-- need to tidy this up and check for override_editor = unknown|not mentioned|?
	local special
	if ed == "?" or string.lower(ed) == "unknown" then
		special = "editor unknown"
	elseif string.lower(ed) == "not mentioned" then
		special = "editor not mentioned"
	elseif ed ~= nil then
		s = s .. "edited by"
	end
	
	s = "<i>" .. s .. "</i> "

	local etext
	local wrap_fn = true
	if args["override_editor"] then
		etext = args["override_editor"]
	elseif args["editor"] then

		if special then
			etext = special
			wrap_fn = false
		else
			etext = "[[Author:" .. args["editor"] .. "|" .. args["editor"] .. "]]"
		end
	end
	
	local span = create_vcard("header_editor_text", etext, true)
	return s .. tostring(span)
end

function p.translator(frame)
	local args = getArgs(frame)

	local tr = get_arg_or_override(args, "translator")

	-- no translator
	if tr == nil then
		return ""
	end

	local have_authors = get_arg_or_override(args, "author") ~= nil
	local have_editors = get_arg_or_override(args, "editor") ~= nil

	local s = " "
	if have_authors or have_editors then
		s = ", "
	end
	
	if not have_authors and not have_authors and args["section"] == nil then
		s = s .. "<br/>"
	end

	local special
	if tr == "?" or string.lower(tr) == "unknown" then
		special = "translator unknown"
	elseif string.lower(tr) == "not mentioned" then
		special = "translator not mentioned"
	elseif string.lower(tr) == "wikisource" then
		special = "translated by [[Wikisource:Translations|<span id=\"header_translator_text\">Wikisource</span>]]"
	elseif tr ~= nil then
		s = s .. "translated by"
	end
	
	s = "<i>" .. s .. "</i> "

	local etext
	local wrap_fn = true
	if args["override_translator"] then
		etext = args["override_translator"]
	elseif args["translator"] then

		if special then
			etext = special
			wrap_fn = false
		else
			etext = "[[Author:" .. args["translator"] .. "|" .. args["translator"] .. "]]"
		end
	end
	
	local span = create_vcard("header_translator_text", etext, true)
	return s .. tostring(span)
end

function p.section(frame)
	local args = getArgs(frame)
	
	if not args["section"] then
		return
	end
	
	local s = "<br /><span id=\"header_section_text\">" .. args["section"] .. "</span>"

	-- there are synonyms for this
	local trans = get_arg_or_override(args, "section_translator")
	if trans == nil then
		trans = get_arg_or_override(args, "contributing_translator")
	end

	-- there are synonyms for this
	local sec_author = get_arg_or_override(args, "section_author")
	if sec_author == nil then
		sec_author = get_arg_or_override(args, "contributor")
	end
	
	-- first part of section_translator, adds a line return to split to two lines due to length if section translator exists	
	if trans and sec_author then
		s = s .. "<br/>"
	end
	
	if sec_author then
		s = s .. "<i> by <span id=\"header_contributor_text\" class=\"vcard\">"
		s = s .. "<span class=\"fn\">"
		
		if args["override_section_author"] then
			s = s .. args["override_section_author"]
		elseif args["override_contributor"] then
			s = s .. args["override_contributor"]
		else
			s = s .. "[[Author:" .. sec_author .. "|" .. sec_author .. "]]"
		end
		s = s .. "</span></span></i>"
	end
	
	if trans then
		if sec_author then
			s = s .. ","	
		end
		s = s .. " <i>translated by <span id=\"header_section_translator_text\" class=\"vcard\">"
		
		if args["override_section_translator"] then
			s = s .. args['override_section_translator']
		else
			s = s .. "[[Author:" .. trans .. "|" .. trans .. "]]"
		end
		s = s .. "</span></span></i>"		
	end
	
	return s
end

--[=[
Construct the notes field
]=]
function p.notes(frame)
	local args = getArgs(frame)
	
	local notes_args = {
		style = "border-bottom: 1px solid #A0A0A0; background-color: #FAFAFF;",
		id = navigationNotes
	}

	local wdid = mw.wikibase.getEntityIdForCurrentPage()
	local sister_args = {"disambiguation", "edition", "portal", "related_author",
		"wikipedia", "commons", "commonscat", "wikiquote", "wikinews", 
		"wiktionary", "wikibooks",  "wikidata", "wikivoyage", 
		"wikiversity", "wikispecies", "meta"}
	local sisters = ""
	if wdid ~= nil or has_any_arg(args, sister_args) then
		notes_args['sister'] = (get_plain_sister(frame, args, sister_args))
	end

    -- TODO: Temporarily track pages sisterlinking to Wikilivres.
    if args["wikilivres"] ~= nil and args["wikilivres"] ~= "" then
        table.insert(categories, "Pages using header with sister links to Wikilivres")
    end

	if args["shortcut"] then
		notes_args['shortcut'] = args["shortcut"]
	end
		
	if args["notes"] then
		notes_args['content'] = args['notes'] 
	end

	return frame:expandTemplate{title = "header/notes block", args = notes_args}
end

return p