Module:Person

From HopperWiki
Jump to navigation Jump to search

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

-- ================================================================================
-- Module dependencies
-- ================================================================================
local cargo = mw.ext.cargo
local getArgs = require('Module:Arguments').getArgs -- for processing arguments
local cf = require("Module:Custom_functions")
local ibf = require("Module:Infobox_functions")
local tf = require("Module:Table_functions")


-- ================================================================================
-- Main table
-- ================================================================================
local p = {}





function p.get_person_gallery_new(frame)
    -- ========================================
    -- Parameters from frame object
    -- ========================================
    local args = getArgs(frame)  -- Use the globally configured getArgs
    local gallery_mode = args.gallery_mode or "packed"
    local name_string = args.names
    
    -- Treat the presence of the parameter as true regardless of its value
    local show_organization = false
    local show_occupation_title = false
    
    -- Converts the user-entered comma-delimited name list to an array
    local name_array = {}
    for value in string.gmatch(name_string, '([^,]+)') do
        table.insert(name_array, mw.text.trim(value))
    end
    
    -- ========================================
    -- Parameters set here
    -- ========================================
    local placeholder_image = 'Face silhouette for no photo profiles.png' -- Define a default placeholder image
    
    -- ========================================
    -- Function to build Cargo where statement
    -- ========================================
	local function build_or_conditions(name_array)
	    local conditions = {}
	    for _, name in ipairs(name_array) do
	        local sanitized_name = mw.text.trim(name):gsub('"', '\\"'):gsub("'", "\\'")
	        table.insert(conditions, string.format('Full_name="%s"', sanitized_name))
	    end
	    return table.concat(conditions, ' OR ')
	end
    
    -- ========================================
    -- Set Cargo variables
    -- ========================================
	local cargo_table = "Person"
	local backup_table = "Person_extra"
    local fields = "Full_name, Image, Organizations, Position_titles"
    local where_condition = build_or_conditions(name_array)
    local cargo_args = {
		where = where_condition
	}
	
	-- ========================================
    -- Get results from primary cargo table results
    -- ========================================
    local primary_results = cargo.query(cargo_table, fields, cargo_args)
    local primary_names_found = {}
    for _, entry in ipairs(primary_results) do
        primary_names_found[entry.Full_name] = true
    end
    
    -- ========================================
    -- Identify missing names and query backup table if necessary
    -- ========================================
	local missing_names = {}
	for _, name in ipairs(name_array) do
	    if not primary_names_found[name] then
	        table.insert(missing_names, name)
	    end
	end
	local backup_results = {}
	if #missing_names > 0 then
	    local backup_where_condition = build_or_conditions(missing_names)
	    local backup_cargo_args = {
	        where = backup_where_condition
	    }
	    backup_results = cargo.query(backup_table, fields, backup_cargo_args)
	end
	
	-- ========================================
    -- Combine results from both tables
    -- ========================================
	local combined_results = {}
	for _, result in ipairs(primary_results) do
	    combined_results[result.Full_name] = {result, "primary"}
	end
	for _, result in ipairs(backup_results) do
	    combined_results[result.Full_name] = {result, "backup"}
	end
	
	-- Overwrite cargo_results with combined results from both tables
	cargo_results = combined_results
    
    -- ========================================
    -- Map results to person name vector
    -- ========================================
	local function get_person_file_names(cargo_results, name_array)
	    local file_name_array = {}
	    for _, name in ipairs(name_array) do
	        local result_info = cargo_results[name]
	        local result = result_info and result_info[1] or nil
	        local table_type = result_info and result_info[2] or "backup"
	        
	        if result and result.Image and result.Image ~= '' then
	            result.Image = cf.remove_file_prefix(result.Image)
	            file_name_array[name] = {
	                Image = cf.add_file_prefix_gentle(result.Image),
	                Organizations = result.Organizations or "Unknown organization",
	                Position_titles = result.Position_titles or "Unknown position",
	                table_type = table_type
	            }
	        else
	            file_name_array[name] = {
		            Image = placeholder_image,
		            Organizations = result and result.Organizations or "Unknown organization",
		            Position_titles = result and result.Position_titles or "Unknown position",
		            table_type = table_type
		        }
	        end
	    end
	    return file_name_array
	end

    local file_name_array = get_person_file_names(cargo_results, name_array)
    
    if next(file_name_array) == nil then
        return '<span class="person-gallery-error">No images found for the provided names!</span>'
    end
    
	-- ========================================
	-- Build the gallery
	-- ========================================
	local gallery_wikitext = '<gallery heights=200 mode="' .. gallery_mode .. '">\n'
	
	for _, name in ipairs(name_array) do
	    local person_info = file_name_array[name]
	    if person_info and person_info.Image then
	        local caption = name  -- Start with just the name
	        local link_to_page = name -- Default link is to the page with the same name
	
	        -- If the name is from the "Person" table, make it a link
	        if person_info.table_type == "primary" then
	            caption = string.format("[[%s|%s]]", link_to_page, name)
	        end
	        
	        -- Append organization if required
	        if show_organization then
	            caption = caption .. "<br>" .. person_info.Organizations
	        end
	        
	        -- Append position titles if required
	        if show_occupation_title then
	            caption = caption .. "<br>" .. person_info.Position_titles
	        end
	
	        -- Add the image with the link to the same page
	        gallery_wikitext = gallery_wikitext .. string.format('%s|link=%s|%s\n', person_info.Image, link_to_page, caption)
	    else
	        gallery_wikitext = gallery_wikitext .. "File:Face silhouette for no photo profiles.png|No image available for " .. name .. '<br><br>\n'
	    end
	end
	
	gallery_wikitext = gallery_wikitext .. '</gallery>'
	
	return frame:preprocess(gallery_wikitext)

end


























-- ================================================================================
-- Get person gallery
-- ================================================================================
function p.get_person_gallery(frame)
    
    -- ========================================
    -- Parameters from frame object
    -- ========================================    
    local args = getArgs(frame)  -- Ensure getArgs is correctly referenced
    local gallery_mode = args.gallery_mode or "packed"
    local name_string = args.names
    
    -- Converts the user-entered comma-delimited name list to an array
    local name_array = {}
    for value in string.gmatch(name_string, '([^,]+)') do
        table.insert(name_array, mw.text.trim(value))
    end
    
    
    -- ========================================
    -- Parameters set here
    -- ========================================    
    local placeholder_image = 'Face silhouette for no photo profiles.png' -- Define a default placeholder image
    
    
    
    -- ========================================
    -- Function to build Cargo where statment
    -- ========================================    
    -- Takes the array of names and builds an "or" conditional statement for the Cargo query in the correct syntax
	local function build_or_conditions(name_array)
	    local conditions = {}
	    for _, name in ipairs(name_array) do
	        -- Trim whitespace and escape single and double quotes
	        local sanitized_name = mw.text.trim(name):gsub('"', '\\"'):gsub("'", "\\'")
	        table.insert(conditions, string.format('Full_name="%s"', sanitized_name))
	    end
	    return table.concat(conditions, ' OR ')
	end
    
    
    -- ========================================
    -- Set Cargo variables
    -- ========================================    
	local cargo_table = "Person"
	local backup_table = "Person_extra"
    local fields = "Full_name, Image, Organizations, Position_titles"
    local where_condition = build_or_conditions(name_array)
    local cargo_args = {
		where = where_condition
	}
	
	
	-- ========================================
    -- Get results from primary cargo table results
    -- ======================================== 
    local primary_results = cargo.query(cargo_table, fields, cargo_args)
    local primary_names_found = {}
    for _, entry in ipairs(primary_results) do
        primary_names_found[entry.Full_name] = true
    end
    
    -- ========================================
    -- Identify missing names and query backup table if necessary
    -- ======================================== 
	local missing_names = {}
	for _, name in ipairs(name_array) do
	    if not primary_names_found[name] then
	        table.insert(missing_names, name)
	    end
	end
	-- Get results from backup cargo table for only for missing names
	local backup_results = {}
	if #missing_names > 0 then
	    local backup_where_condition = build_or_conditions(missing_names)
	    local backup_cargo_args = {
	        where = backup_where_condition
	    }
	    backup_results = cargo.query(backup_table, fields, backup_cargo_args)
	end
	
	
	-- ========================================
    -- Combine results from both tables
    -- ========================================
	local combined_results = {}
	for _, result in ipairs(primary_results) do
	    combined_results[result.Full_name] = result
	end
	for _, result in ipairs(backup_results) do
	    combined_results[result.Full_name] = result
	end
	
	-- Overwrite cargo_results with combined results from both tables
	cargo_results = combined_results
    
    
    -- ========================================
    -- Map results to person name vector
    -- ========================================
    -- Function that extracts Cargo results and maps them onto the user-provided name array
	local function get_person_file_names(cargo_results, name_array)
	    -- Use a table to map names to their images, organizations, and positions
	    local file_name_array = {}
	    -- Iterate over the ordered name array
	    for _, name in ipairs(name_array) do
	        local result = cargo_results[name]
	         if result and result.Image and result.Image ~= '' then
	            
	            -- remove File prefix if it already exists
	            result.Image = cf.remove_file_prefix(result.Image)
	            
	            
	            -- Only add entries to file_name_array if an image is available
	            file_name_array[name] = {
	                Image = cf.add_file_prefix_gentle(result.Image),
	                Organizations = result.Organizations or "Unknown organization",
	                Position_titles = result.Position_titles or "Unknown position"
	            }
	        else
	        	
	        	-- Handle cases where no results are found or no image is available
	            -- Use the default placeholder image if no image is found
		        file_name_array[name] = {
		            Image = placeholder_image,
		            Organizations = result and result.Organizations or "Unknown organization",
		            Position_titles = result and result.Position_titles or "Unknown position"
		        }
	        end
	    end
	    return file_name_array
	end
    -- Takes the array of names and returns a table mapping each name to its corresponding image file
    local file_name_array = get_person_file_names(cargo_results, name_array)
    
    -- Check if the mapping table is empty (indicating no images were found)
    if next(file_name_array) == nil then
        return '<span class="person-gallery-error">No images found for the provided names!</span>'
    end
    
    
    
    -- ========================================
    -- Build the gallery
    -- ========================================
	-- Adjust the gallery_wikitext appending to include organization and position titles
        local gallery_wikitext = '<gallery heights=200 mode="' .. gallery_mode .. '">\n'
    
    for _, name in ipairs(name_array) do
        local person_info = file_name_array[name]
        if person_info and person_info.Image then
            -- Use <br> instead of \n for line breaks in captions
            local caption = string.format("%s<br>%s<br>%s", name, person_info.Organizations, person_info.Position_titles)
            gallery_wikitext = gallery_wikitext .. person_info.Image .. '|' .. caption .. '\n'
        else
            -- Handle names without associated images or data with a placeholder
            gallery_wikitext = gallery_wikitext .. "File:Face silhouette for no photo profiles.png|No image available for " .. name .. '<br><br>\n'
        end
    end
    
    gallery_wikitext = gallery_wikitext .. '</gallery>'
    
    return frame:preprocess(gallery_wikitext)
	
end






-- ================================================================================
-- Create a person bio box
-- ================================================================================
function p.get_person_bio_box(frame)    
	
	
	-- get args passed through the frame
    local args = getArgs(frame)
	local title = mw.title.getCurrentTitle().text
	local name = args.name or title
	local position_title = args.position_title 
	
	
    -- Cargo query to retrieve user image and description
    local tables = "Person"
    local fields = "Full_name, Image, Position_titles, Bio"
    -- local where = string.format('Full_name = "%s"', mw.text.trim(name))
    local where = string.format('Full_name = "%s"', name)
    local cargo_results = mw.ext.cargo.query(tables, fields, {where = where})
    
    if #cargo_results < 1 then
    	return string.format('<span style="color: red;">%s</span>', "Can't find this name in the Cargo database!")
	end
    
    
    -- control loop for image file name
    local image = cargo_results[1].Image and cargo_results[1].Image ~= "" and cargo_results[1].Image or "Face silhouette for no photo profiles.png"
    
    -- add file prefix if needed
    image = cf.add_file_prefix_gentle(image)
    
    
    -- control loop for position titles
    local position_titles = args.position_title or cargo_results[1].Position_titles


	-- begin create html container
	local container = mw.html.create('div')
		:addClass('person-photo-box')

    -- Header with the person's name and title
    container:tag('div')
        :addClass('person-photo-header')
        :wikitext(string.format('[[%s|%s%s%s]]', name, name, " - ", position_titles))

    if cargo_results and #cargo_results > 0 then
        local contentWrapper = mw.html.create('div')
            :addClass('content-wrapper')
        
        local imageDiv = mw.html.create('div')
            :addClass('person-photo-content')
            :wikitext(string.format('[[%s%s%s]]', image, '|link=', name))


        local textDiv = mw.html.create('div')
            :addClass('person-photo-text')
            :wikitext(cargo_results[1].Bio or "")

        -- Append image and text divs to the content wrapper
        contentWrapper
            :node(imageDiv)
            :node(textDiv)

        -- Append content wrapper to the container
        container:node(contentWrapper)
    else
        container:tag('div')
            :addClass('error-message')
            :wikitext(string.format('<span style="color: red;">%s</span>', "Can't find this name in the Cargo database!"))
    end

    return tostring(container)
end






-- ================================================================================
-- Get person photo
-- ================================================================================
local function get_person_photo(frame)
	
	local args = getArgs(frame)
	local title = mw.title.getCurrentTitle().text
	local name = args.name or title

	-- cargo query to retrieve user image
    local tables = "Person"
	local fields = "Full_name, Image"
    local cargo_args = {
    	where = string.format('Full_name = "%s"', name)
	}
	
	local cargo_results = cargo.query(tables, fields, cargo_args)
	
	-- extract image name
	local image = cargo_results[1].Image
	-- add file prefix if needed
	image = cf.add_file_prefix_gentle(image)
	
	if cargo_results and #cargo_results > 0 then
		 -- return user bio
    	return "[[" .. image .. "]]"
    else
    	 -- return error message in red
        return string.format('<span style="color: red;">%s</span>', "Can't find this name in the Cargo database!")
	end

end
p.get_person_photo = get_person_photo






















-- ================================================================================
-- Infobox
-- ================================================================================
function p.infobox(frame)
	
	local args = getArgs(frame)
	local title = mw.title.getCurrentTitle().text
	
	
	--[[ this is important to escape single and double quotes in titles for Cargo queries and text display but I need an 
	un-escaped version for the create_infobox() function below because that function escapes apostrophes in names in a different way
	for html rendering.
	--]]
    local escaped_title = title:gsub("'", "''"):gsub('"', '""')
	
	
	args.Image  = args.Image or "Face silhouette for no photo profiles.png"
	
	
	
	-- ========================================
    -- Cargo query for User page connection
    -- ======================================== 
    -- This Cargo query is necessary to get the connection to the mediawiki user page for a given person
	local tables = "User_to_person"
    local fields = "Full_name"
    local cargo_args = {
        where = string.format("%s = '%s'", "Full_name", escaped_title)
    }
    local cargo_results = cargo.query(tables, fields, cargo_args)
	
	
	-- ========================================
    -- Build HTML
    -- ========================================    
	local link_text = "URL"
	local root = ""
	local html_theme_class = '-user'
	root = ibf.create_infobox(root, title, html_theme_class)	
	root = ibf.add_image(root, args.Image, html_theme_class)
    root = ibf.add_row(root, 'Country', args.Base_country, html_theme_class, "page")
    root = ibf.add_row(root, 'Organizations:', args.Organizations, html_theme_class, "")
	root = ibf.add_row(root, 'Departments', args.Departments, html_theme_class)
	root = ibf.add_row(root, 'Titles:', args.Position_titles, html_theme_class)
	root = ibf.add_row(root, 'Focal areas:', args.Focal_areas, html_theme_class)
	root = ibf.add_row(root, 'Focal species:', args.Focal_species, html_theme_class, "page")
	root = ibf.add_row(root, 'Website:', args.Other_websites, html_theme_class, "ext_link", link_text)
	root = ibf.add_row(root, 'LinkedIn:', args.Linkedin, html_theme_class, "ext_link", link_text)
	root = ibf.add_row(root, 'Twitter:', args.Twitter, html_theme_class, "ext_link", link_text)
	
    return root
   
end






-- ================================================================================
-- Retrieve a Person biography
-- ================================================================================
local function biography(frame)
	
	local args = getArgs(frame)
	local title = mw.title.getCurrentTitle().text
	
	local name = args.name or title

	-- cargo query to retrieve user bio
    local tables = "Person"
	local fields = "Full_name, Bio"
    local cargo_args = {
    	where = string.format('Full_name = "%s"', name)
	}
	
	local cargo_results = cargo.query(tables, fields, cargo_args)
	
	if cargo_results and #cargo_results > 0 then
		 -- return user bio
    	return cargo_results[1]["Bio"]
    else
    	 -- return error message in red
        return string.format('<span style="color: red;">%s</span>', "Can't find this name in the Cargo database!")
	end

end
p.biography = biography



return p