Give Witnesses Their Own Facts.fh_lua

--[[
@Title:			Give Witnesses Their Own Facts
@Type:				Standard
@Author:			Mike Tate
@Contributors:	
@Version:			1.5
@Keywords:		
@LastUpdated:		11 Feb 2026
@Licence:			This plugin is copyright (c) 2026 Mike Tate & contributors and is licensed under the MIT License which is hereby incorporated by reference (see https://pluginstore.family-historian.co.uk/fh-plugin-licence)
@Description:		Remove chosen Individual Witnesses from Facts and create their own Facts instead.
@V1.5:				Centre windows on FH window; Check Version in Store button; fhInitialise();
@V1.4:				Handle rich text notes and citation metafields; Privacy brackets option; Add Family Residence; Cater for _SDATE;
@V1.3:				FH V7 Lua 3.5 IUP 3.28;
@V1.2:				Avoid duplicating any Fact when same Individual is a multiple Principal/Witness, plus copy all Role Citations;
@V1.1:				Added Census (family);
@V1.0:				First published in Plugin Store;
]]

require "iuplua"
iup.SetGlobal("CUSTOMQUITMESSAGE","YES")									-- Needed for IUP 3.28

local strVersion = "1.5"														-- Update when version changes

--[[
@Function:		CheckVersionInStore
@Author:			Mike Tate
@Version:			1.4
@LastUpdated:		20 Jan 2026
@Description:		Check plugin version against version in Plugin Store
@Parameter:		Plugin name and version
@Returns:			None
@Requires:		luacom
@V1.4:				Dispense with files and assume called via IUP button;
@V1.3:				Save and retrieve latest version in file;
@V1.2:				Ensure the Plugin Data folder exists;
@V1.1:				Monthly interval between checks; Report if Internet is inaccessible;
@V1.0:				Initial version;
]]

function CheckVersionInStore(strPlugin,strVersion)						-- Check if later Version available in Plugin Store

	require "luacom"

	local function httpRequest(strRequest)									-- Luacom http request protected by pcall() below
		local http = luacom.CreateObject("winhttp.winhttprequest.5.1")
		http:Open("GET",strRequest,false)
		http:Send()
		return http.Responsebody
	end -- local function httpRequest

	local function intVersion(strVersion)									-- Convert version string to comparable integer
		local intVersion = 0
		local arrNumbers = {}
		strVersion:gsub("(%d+)", function(strDigits) table.insert(arrNumbers,strDigits) end)
		for i = 1, 5 do
			intVersion = intVersion * 100 + tonumber(arrNumbers[i] or 0)
		end
		return intVersion
	end -- local function intVersion

	local strLatest = "0"
	if strPlugin then
		local strRequest ="http://www.family-historian.co.uk/lnk/checkpluginversion.php?name="..tostring(strPlugin)
		local isOK, strReturn = pcall(httpRequest,strRequest)
		if not isOK then															-- Problem with Internet access
			fhMessageBox(strReturn.."\n The Internet appears to be inaccessible. ")
		elseif strReturn then
			strLatest = strReturn:match("([%d%.]*),%d*")					-- Version digits & dots then comma and Id digits 
		end
	end
	local strMessage = "No later Version"
	if intVersion(strLatest) > intVersion(strVersion or "0") then
		strMessage = "Later Version "..strLatest
	end
	fhMessageBox(strMessage.." of this Plugin is available from the 'Plugin Store'.")
end -- function CheckVersionInStore

function getParamEmulator()													-- 	Prototype for iup.GetParam(...)	-- V1.5

	if fhGetAppVersion() > 6 then unpack = table.unpack end				-- Needed for Lua 5.3

	local iupDialog = nil
	local arrValues = {}
	local isSuccess = false
	local strPlugin = fhGetContextInfo("CI_PLUGIN_NAME"):gsub(" %- .*","")

	local function handleButton(iupDialog,intIndex,strTitle)				-- Handle the dialog buttons
		if intIndex == (iup.GETPARAM_OK or -1) then
			-- strTitle sometimes needed to determine the function		-- 1st button action				-- FH V5 needs -1
			isSuccess = true
		elseif intIndex == (iup.GETPARAM_CANCEL or -3) then				-- 2nd 'Cancel Plugin' button		-- FH V5 needs -3
			isSuccess = false
		elseif intIndex == (iup.GETPARAM_HELP or -4) then					-- 3rd 'Later Version?' button	-- FH V5 needs -4
			iupDialog.Active = "NO"
			CheckVersionInStore(strPlugin,strVersion)
			iupDialog.Active = "YES"
			iupDialog.BringFront = "YES"
			return 0
		end
		return 1
	end -- function handleButton

	local function makeDialog(strTitle,strFormat)							-- Make emulated iup.GetParam(...) dialog
		local arrFormat = {}
		for strForm in strFormat:gmatch(".-\n") do							-- Construct parameters from format
			local iupParam = iup.param{ format=strForm; }
			table.insert(arrFormat,iupParam)
		end

		local iupParams = iup.parambox{ unpack(arrFormat) }
		local iupButton = iup.button{ Title="Help && Advice"; Padding="12x8"; }	-- Example of extra button
--		iupDialog = iup.dialog{ Title=strTitle; iup.vbox{ iupParams; iupButton; ALIGNMENT="ACENTER"; MARGIN="10x10"; }; close_cb=function() isSuccess = false return iup.CLOSE end; }
		iupDialog = iup.dialog{ Title=strTitle; iupParams; close_cb=function() isSuccess = false return iup.CLOSE end; }
		if fhGetAppVersion() > 6 then 										-- Window centres on FH parent
			iup.SetAttribute(iupDialog,"NATIVEPARENT",fhGetContextInfo("CI_PARENT_HWND"))
		end
		for intParam = 1, iupParams.ParamCount do							-- Set all parameter values
			local iupParam = iupParams:GetParamParam(intParam-1)
			local iupCntrl = iupParam.Control
			local anyValue = arrValues[intParam]
			if iupParam.Type == "LIST" then anyValue = anyValue + 1 end	-- Droplists need an adjustment
			iupCntrl.Value = anyValue
		end

		function iupParams:param_cb(intIndex)								-- Parameter call back actions
			if intIndex >= 0 then
				local iupParam = iupParams:GetParamParam(intIndex)		-- Save any parameter value
				arrValues[intIndex+1] = tonumber(iupParam.Value) or iupParam.Value
				return 1
			else
				return handleButton(iupDialog,intIndex,strTitle)			-- Handle buttons
			end
		end -- function iupParams:param_cb

		function iupButton:action(intButton)									-- Display Help Page
			local strPlugin = strPlugin:gsub(" ","-"):lower()
			fhShellExecute("https://pluginstore.family-historian.co.uk/page/help/"..strPlugin,"","","open")
			fhSleep(3000,500)
			iupDialog.BringFront = "YES"
			return 1
		end -- function iupButton:action

	end -- local function makeDialog

	local function getParam(strTitle,strSize,strFormat,...)				-- Emulate iup.GetParam(...) for FH V7 or later
		arrValues = {...}
		if fhGetAppVersion() > 6 then
			makeDialog(strTitle,strFormat)
			if strSize then iupDialog.Size = strSize end
			iupDialog:map()
			iupDialog.MinSize = iupDialog.NaturalSize
			iupDialog:showxy(iup.CENTERPARENT,iup.CENTERPARENT)
			if iup.MainLoopLevel()==0 then iup.MainLoop() end
			iup.Destroy(iupDialog)
			iupDialog = nil
		else
			local function fncAction(iupDialog,intIndex)
				return handleButton(iupDialog,intIndex,strTitle)			-- Handle buttons
			end -- local function fncAction

			arrValues = { iup.GetParam(strTitle,fncAction,strFormat,unpack(arrValues)) }
			isSuccess = arrValues[1]
			table.remove(arrValues,1)	
		end
		return isSuccess, unpack(arrValues)
	end -- local function getParam

	return getParam

end -- function getParamEmulator

local getParam = getParamEmulator()

function Find(dicFact)															-- Find the Facts that have Witness Roles
	local intFind = 0
	local ptrRec = fhNewItemPtr()
	for intType, strType in ipairs ( { "INDI"; "FAM"; } ) do				-- V1.1
		ptrRec:MoveToFirstRecord(strType)										-- Loop through all Individual & Family Records
		while ptrRec:IsNotNull() do
			local ptrFact = fhNewItemPtr()
			ptrFact:MoveToFirstChildItem(ptrRec)								-- Loop through all Facts
			while ptrFact:IsNotNull() do
				local strFact = fhGetTag(ptrFact)
				if fhIsFact(ptrFact)										 	-- Any Individual Fact or Family Census or Family Residence -- V1.1 -- V1.4
				and ( strType == "INDI" or strFact == "CENS" or strFact == "RESI" ) then
					local ptrShar = fhNewItemPtr()
					ptrShar:MoveTo(ptrFact,"~._SHAR")							-- Loop through all Fact Witness Roles
					while ptrShar:IsNotNull() do
						local strName = strFact..strType						-- V1.1
						local strRole = fhGetItemText(ptrShar,"~.ROLE")
						if not dicFact[strName] then
							dicFact[strName] = {}								-- Add new Fact and its Label
							dicFact[strName][0] = fhCallBuiltInFunction("FactLabel",ptrFact)
						end
						local intRole = dicFact[strName][strRole]
						if not intRole then
							intFind = intFind + 1								-- Count different Fact Witness Roles 
							intRole = 0
						end
						dicFact[strName][strRole] = intRole + 1				-- Count duplicate Fact Witness Roles
						ptrShar:MoveNext("SAME_TAG")
					end
				end
				ptrFact:MoveNext()
			end
			ptrRec:MoveNext()
		end
	end
	dicFact[0] = intFind														-- Save count of different Roles found
	return ( intFind > 0 )
end -- function Find

function Pick(dicFact)															-- Pick which Witness Roles to give own Facts
	local arrReply = { }														-- GetParam reply tick values
	local isTicked = true														-- GetParam reply status
	local intParam = 0															-- Count of params per page
	local strParam = ""															-- Format of params per page
	local intSize = 25															-- Maximum params per page
	local intPage = 1															-- Current page number
	local intLast = math.ceil( dicFact[0] / intSize )						-- Number of pages needed
	intSize = math.ceil( dicFact[0] / intLast )								-- Even out params per page
	dicFact[0] = nil

	local function Zeros(intParam)											-- Return same number of 0's as Witness Role params
		intParam = intParam - 1
		if intParam == 0 then return 0 end									-- End recursion
		return 0,Zeros(intParam)
	end -- local function Zeros

	local function GetParam(strButton)										-- GetParam user dialogue with OK button name	-- V1.5
		strParam = "Tick the Witness Roles to be given their own Facts: %t\n"..strParam
		strParam = strParam.."For all Roles ticked above, this Plugin copies the  \r"
		strParam = strParam.."principal Fact on the left to each of its Witnesses,\r"
		if dicFact["CENSFAM"] then
		 strParam = strParam.."(Any 'Census (family)' becomes 'Census' events.)   \r"
		end
		strParam = strParam.."then provides a Result Set list of all the changes. \r"
		strParam = strParam.."Undo changes with 'Edit > Undo Plugin Updates'      \r"
		strParam = strParam.."or 'File > Backup/Restore > Revert to Snapshot'.     %t\n"
		strParam = strParam.."%u[  "..strButton.."  ,  Close Plugin  ,  Later Version?  ]\n"	-- V1.5
		local strTitle = " Select Witness Roles "..strVersion.."  Page "..intPage.." of "..intLast
		local arrParam = { getParam( strTitle, nil, strParam, Zeros(intParam) ) }					-- V1.5
		isTicked = arrParam[1]													-- Save reply status: true=OK, false=Cancel
		for intParam = 2, #arrParam do
			table.insert(arrReply,arrParam[intParam])						-- Append reply ticks to previous replies
		end
	end -- local function GetParam

	for strFact, arrRole in pairs (dicFact) do								-- Loop through all Fact Witness Roles
		local strLabel = arrRole[0]
		strLabel = strLabel..string.rep("  ",13 - #strLabel)				-- Suffix Fact Label with spaces to fixed width
		arrRole[0] = nil
		for strRole, intRole in pairs (arrRole) do							-- Compose GetParam format text of boolean per Role
			intParam = intParam + 1
			local strCount = tostring(intRole)								-- Prefix Role count with spaces to fixed width
			strCount = string.rep("  ",5 - #strCount)..strCount.."      x      "
			strParam = strParam..strCount..strLabel.."   ~      "..strRole.." %b\n"
			if intPage < intLast and intParam >= intSize then
				GetParam("Next Page")											-- Get a page of user params	-- V1.5
				if not isTicked then break end
				intParam = 0
				strParam = ""														-- Reset for next page
				intPage = intPage + 1
			end
		end
		if not isTicked then break end										-- User cancelled dialogue
	end
	if isTicked then
		strParam = strParam.."Put each Witness Role note in [[privacy]] brackets? %b\n"
		intParam = intParam + 1													-- Privacy brackets?	-- V1.4
		GetParam("Copy Facts")													-- Get last page of user params	-- V1.5
		if isTicked then
			isTicked = false
			for strName, arrRole in pairs (dicFact) do						-- Check if any ticks -- V1.1
				for strRole, intRole in pairs (arrRole) do
					local isTick = (table.remove(arrReply,1) == 1)			-- Reply = 1 if tick and thus true -- V1.1
					dicFact[strName][strRole] = isTick						-- Assign tick is true or false to each Role -- V1.1
					isTicked = isTicked or isTick
				end
			end
			dicFact.Privacy = (arrReply[#arrReply] == 1)					-- Privacy brackets?	-- V1.4
		end
	end
	return isTicked
end -- function Pick

function Error(strAct,...)														-- Report error and abort the plugin
	local arg = {...}
	local strErr = "\n"..strAct.." (\n "
	for intArg = 1, #arg do
		local strArg = arg[intArg]
		local strTyp = type(strArg)											-- Convert pointer or boolean args to text
		if strTyp == "userdata" then
			strArg = fhGetDisplayText(strArg)
		elseif strTyp == "boolean" then
			strArg = tostring(strArg)
		end
		strErr = strErr..strArg
		if intArg < #arg then strErr = strErr.." ,\n " end
	end
	strErr = strErr.." )\nfailed"
	local intArg = 1
	if type(arg[intArg]) == "string"  then intArg = #arg end				-- Find target pointer
	if type(arg[intArg]) == "boolean" then intArg = intArg-1 end
	local ptrRec = arg[intArg]:Clone()
	if fhHasParentItem(ptrRec) then											-- Is target a record?
		ptrRec:MoveToRecordItem(ptrRec)
		strErr = strErr.." for "..fhGetDisplayText(ptrRec)				-- Add name of record
	end
	error("\n\nError: "..strErr,3)											-- Report and abort
end -- function Error

dicAct = { fhCreateItem=fhCreateItem; fhSetValue_Copy=fhSetValue_Copy; fhGetValueAsText=fhGetValueAsText; fhGetValueAsRichText=fhGetValueAsRichText; fhGetValueAsLink=fhGetValueAsLink; fhSetValueAsText=fhSetValueAsText; fhSetValueAsRichText=fhSetValueAsRichText; fhSetValueAsLink=fhSetValueAsLink; fhDeleteItem=fhDeleteItem; }

function Perform(strAct,...)													-- Perform FH API function
	local anyAns = dicAct[strAct](...)
	if ( type(anyAns) == "boolean"  and not anyAns )						-- Most return true or false
	or ( type(anyAns) == "userdata" and anyAns:IsNull() )					-- fhCreateItem returns pointer
	then Error(strAct,...) end
	return anyAns																	-- Others can return text
end -- function Perform

function CopyBranch(ptrSource,ptrTarget)									-- Copy one child branch
	local strTag = fhGetTag(ptrSource)
	if strTag == "HUSB" or strTag == "WIFE" then return end				-- Family Census tags not allowed in Individual Census -- V1.1
	if strTag == "_FMT" then return end 										-- Skip rich text format tag -- V1.4
	if strTag == "_FIELD" then
		strTag = fhGetMetafieldShortcut(ptrSource)							-- Handle citation metafield -- V1.4
	end
	local ptrNew = Perform("fhCreateItem",strTag,ptrTarget,true)
	Perform("fhSetValue_Copy",ptrNew,ptrSource)
	CopyChildren(ptrSource,ptrNew)
end -- function CopyBranch

local dicExclude = { AGE=true; _SHAR=true; _SHAN=true; _SENT=true; }	-- V1.4

function CopyChildren(ptrSource,ptrTarget)									-- Copy children branches
	local ptrFrom = fhNewItemPtr()
	ptrFrom = ptrSource:Clone()
	ptrFrom:MoveToFirstChildItem(ptrFrom)
	while ptrFrom:IsNotNull() do
		local strTag = fhGetTag(ptrFrom)
		if not dicExclude[strTag] then								 		-- Exclude AGE and custom tags _SHAR, _SHAN, _SENT, but not _SDATE	-- V1.4
			CopyBranch(ptrFrom,ptrTarget)
		end
		ptrFrom:MoveNext()
	end
end -- function CopyChildren

function PerRidData(tblRid,intRid,strNote,ptrFact,ptrRole)				-- Get/Set per RecId for Fact & Note & Role -- V1.2
	local dicRid = tblRid[intRid]
	if not dicRid or ptrRole then
		dicRid = dicRid or {}
		dicRid.Note = strNote or ""
		dicRid.Fact = ptrFact or fhNewItemPtr()
		dicRid.Role = ptrRole or fhNewItemPtr()
		tblRid[intRid] = dicRid
	end
	return dicRid
end -- function PerRidData

function Make(dicFact)															-- Make the Facts for chosen Witness Roles
	local intMake = 0
	local arrRec  = {}															-- Result Set tables
	local arrRole = {}
	local arrFact = {}
	local strPref = ""
	local strSuff = ""
	if dicFact.Privacy then														-- Privacy brackets?	-- V1.4
		strPref = "[["
		strSuff = "]]"
	end
	local ptrRec  = fhNewItemPtr()
	for intType, strType in ipairs ( { "INDI"; "FAM"; } ) do				-- V1.1
		ptrRec:MoveToFirstRecord(strType)										-- Loop through all Individual & Family Records
		while ptrRec:IsNotNull() do
			local ptrFact = fhNewItemPtr()
			ptrFact:MoveToFirstChildItem(ptrRec)								-- Loop through all Facts
			while ptrFact:IsNotNull() do
				local strFact = fhGetTag(ptrFact)
				if fhIsFact(ptrFact)										 	-- Any Individual Fact or Family Census or Family Residence -- V1.1 -- V1.4
				and ( strType == "INDI" or strFact == "CENS" or strFact == "RESI" ) then
					local isPrincipal = true
					local tblRid = {}											-- Table per RecId for Fact & Note & Role -- V1.2
					local ptrShar = fhNewItemPtr()
					ptrShar:MoveTo(ptrFact,"~._SHAR")							-- Loop through all Fact Witness Roles
					while ptrShar:IsNotNull() do
						local strName = strFact..strType						-- V1.1
						local ptrRole = ptrShar:Clone()
						local strRole = fhGetItemText(ptrRole,"~.ROLE")
						ptrShar:MoveNext("SAME_TAG")
						if dicFact[strName][strRole] then						-- Give the Witness their own Fact? -- V1.1
							if isPrincipal then
								table.insert(arrRec ,ptrRec:Clone())			-- Update the Result Set tables for Principal
								table.insert(arrRole,"PRINCIPAL")
								table.insert(arrFact,ptrFact:Clone())
								local intRid  = fhGetRecordId(ptrRec)		-- Save principal Note & Fact & no Role -- V1.2
								PerRidData(tblRid,intRid,strPref.."Principal Role"..strSuff.."\n",ptrFact)	-- V1.4
								isPrincipal = false
							end
							local ptrWitn = fhGetValueAsLink(ptrRole)		-- Find the Witness and their RecId
							local intRid  = fhGetRecordId(ptrWitn)			-- V1.2
							local dicRid  = PerRidData(tblRid,intRid)		-- Get Note & Fact & Role for RecId, may be from principal -- V1.2
							local strNote = dicRid.Note..strPref.."Witness Role: "..strRole..strSuff.."\n"	-- V1.4
							local ptrCopy = dicRid.Fact
							if ptrCopy:IsNull() then							-- Create a copy of principal Fact ? -- V1.2
								ptrCopy = Perform("fhCreateItem",strFact,ptrWitn)
								Perform("fhSetValue_Copy",ptrCopy,ptrFact)	-- Copy all principal Fact fields except AGE, _SHAR, _SHAN, _SENT, etc
								CopyChildren(ptrFact,ptrCopy)
							end 													-- Save witness Note & Fact & Role for RecId -- V1.2
							tblRid[intRid] = PerRidData(tblRid,intRid,strNote,ptrCopy,ptrRole)
							table.insert(arrRec ,ptrWitn:Clone())			-- Update the Result Set tables
							table.insert(arrRole,strRole)
							table.insert(arrFact,ptrCopy:Clone())
							dicFact[strType] = true								-- Signal that INDI/FAM Witnesses have been handled, but only needed for Census (family)
						end
					end
					for intRid, dicRid in pairs (tblRid) do					-- Add the Role text to each Fact Note and copy any Role Citations -- V1.2
						local strNote = dicRid.Note							-- Cannot add earlier if Principal is own Witness as gets copied to every other Witness
						local ptrFact = dicRid.Fact
						local ptrRole = dicRid.Role
						local strMode = "fhSetValueAsText"					-- V1.4	-- Can this plain & rich text manipulation be simplified?
						local ptrNote = fhGetItemPtr(ptrRole,"~.NOTE2")
						if fhGetValueType(ptrNote) == "richtext" then		-- Witness Role note is rich text	-- V1.4
							local strRich = fhNewRichText(strNote)
							strRich:AddRichText(fhGetValueAsRichText(ptrNote))
							strNote = strRich
							strMode = "fhSetValueAsRichText"
						else
							strNote = strNote..fhGetValueAsText(ptrNote)	-- Witness Role note is plain text
						end
						local ptrNote = fhGetItemPtr(ptrFact,"~.NOTE2")
						if ptrNote:IsNull() then							 	-- Add a local Note to the Fact
							ptrNote = Perform("fhCreateItem","NOTE2",ptrFact)
						else														-- Get existing local Note and Witness Role note
							if fhGetValueType(ptrNote) == "richtext" then	-- Local Note is rich text			-- V1.4
								local strRich = fhGetValueAsRichText(ptrNote)
								if strMode == "fhSetValueAsRichText" then	-- Witness Role note is rich text	-- V1.4
									strRich:AddRichText(fhNewRichText("\n"))
									strRich:AddRichText(strNote)
								else
									strRich:AddRichText(fhNewRichText("\n"..strNote))
								end
								strNote = strRich
								strMode = "fhSetValueAsRichText"
							elseif strMode == "fhSetValueAsRichText" then	-- Local Note is plain text but Witness Role note is rich text	-- V1.4
								local strRich = fhNewRichText(fhGetValueAsText(ptrNote).."\n")
								strRich:AddRichText(strNote)
							else													-- Local Note & Witness Role note are plain txt
								strNote = fhGetValueAsText(ptrNote).."\n"..strNote
							end
						end
						Perform(strMode,ptrNote,strNote)						-- Update the local Note with Witness Role note	-- V1.4
						for strTag, strType in pairs ({SOUR="Link";SOUR2="Text";}) do
							local ptrCite = fhNewItemPtr()
							ptrCite:MoveTo(ptrRole,"~."..strTag)				-- Loop through all Role Citations and copy them
							while ptrCite:IsNotNull() do
								local anyType = Perform("fhGetValueAs"..strType,ptrCite)
								local ptrSour = Perform("fhCreateItem",strTag,ptrFact)
								Perform("fhSetValueAs"..strType,ptrSour,anyType)
								CopyChildren(ptrCite,ptrSour)					-- Copy citation subsidiary fields	-- V1.4
								ptrCite:MoveNext("SAME_TAG")
							end
						end
						if ptrRole:IsNotNull() then
							Perform("fhDeleteItem",ptrRole)					-- Delete original Witness Role (i.e. when not just Principal)
							intMake = intMake + 1								-- Count them
						end
					end
				end
				ptrFact:MoveNext()
			end
			ptrRec:MoveNext()
		end
	end
	if #arrRec > 0 then															-- Output Result Set
		fhOutputResultSetTitles("Give Witnesses Their Own Facts "..strVersion)
		fhOutputResultSetColumn("Record","item",arrRec ,#arrRec,120,"align_left")
		fhOutputResultSetColumn("Role"  ,"text",arrRole,#arrRec, 50,"align_left")
		fhOutputResultSetColumn("Fact"  ,"item",arrFact,#arrRec,300,"align_left")
	end
	return intMake, intMike
end -- function Make()

function Main()
	local dicFact = {}															-- Dictionary of Facts with Witness Roles
	if Find(dicFact) then														-- Find the Facts that have Witness Roles
		if Pick(dicFact) then													-- Pick the Witness Roles to get own Facts
			if "Yes" == fhMessageBox("\n Are you sure you can recover from unwanted changes? \n e.g. \n Used    'File > Backup/Restore > Small Backup' \n","MB_YESNO","MB_ICONQUESTION") then
				local intMake = Make(dicFact)									-- Make the Facts for chosen Witness Roles
				local strHelp = ""
				if dicFact["FAM"] then											-- Census (family) Witnesses were removed
					strHelp = "\n Consider using 'Migrate Census Family to Individual Events' Plugin. \n"
				end
				local strMake = " Witnesses were given their own Individual Facts. \n"
				if intMake == 1 then
					strMake = strMake:gsub("es "," "):gsub("s%. ",". ")
				end
				fhMessageBox("\n "..intMake..strMake.."\n Undo changes with 'Edit > Undo Plugin Updates' \n or 'File > Backup/Restore > Revert to Snapshot'.\n"..strHelp)
			end
		else
			fhMessageBox("\n No Individual Fact Witnesses Changed. \n")
		end
	else
		fhMessageBox("\n No Individual Fact Witnesses Found. \n")
	end
end -- function Main()

-- Main Code Section Starts Here --

fhInitialise(6,0,0,"save_recommended")										-- V1.5

Main()

Source:Give-Witnesses-Their-Own-Facts-3.fh_lua