Add Media URL Shortcut.fh_lua

--[[
@Title:			Add Media URL Shortcut
@Type:				Standard
@Author:			Mike Tate
@Contributors:	
@Version:			1.3
@Keywords:		
@LastUpdated:	26 Nov 2020
@Licence:			This plugin is copyright (c) 2020 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:	Add a Media tab link to a Media record for a URL Shortcut to a web page.
@V1.3:				FH V7 Lua 3.5 IUP 3.28 compatible version;
@V1.2:				Allow the URL to contain Lua magic pattern %n capture index.
@V1.1:				Allows a Title name for new Media record.
@V1.0:				First published in Plugin Store.
]]

require "iuplua"														-- To access GUI window builder
require "lfs"															-- To access LUA filing system

function OpenFile(strFileName,strMode)								-- Open File and return Handle
	local fileHandle, strError = io.open(strFileName,strMode)
	if fileHandle == nil then
		error("\n Unable to open file in \""..strMode.."\" mode. \n "..strFileName.." \n "..strError.." \n")
	end
	return fileHandle
end -- function OpenFile

function SaveStringToFile(strString,strFileName)				-- Save text string to file
	local fileHandle = OpenFile(strFileName,"w")
	fileHandle:write(strString)
	assert(fileHandle:close())
end -- function SaveStringToFile

function strMakeShortcutFile(strURL)								-- Make the URL Shortcut File (.url)
	local strShortcut =													-- URL Shortcut file template
	[[
		[{000214A0-0000-0000-C000-000000000046}]
		Prop3=19,2
		[InternetShortcut]
		URL=
		IDList=
	]]
	local tblPattern = {}												-- Filename encodings for disallowed chars \/:"<>|*?
	tblPattern['"'] = "%22"
	tblPattern["*"] = "%2A"
	tblPattern["/"] = " "
	tblPattern[":"] = "%3A"
	tblPattern["<"] = "%3C"
	tblPattern[">"] = "%3E"
	tblPattern["?"] = "%3F"
	tblPattern["\\"]= "%5C"
	tblPattern["|"] = "%7C"
	local strFileName = strURL:gsub("://"," "):gsub('[\\/:"<>|%*%?%.]',tblPattern)..".url"
	local strMediaDir = fhGetContextInfo("CI_PROJECT_DATA_FOLDER").."\\Media\\"
	local strMediaURL = strMediaDir.."URL\\"
	lfs.mkdir(strMediaDir)												-- Ensure ...\Media\URL\ folders exist
	lfs.mkdir(strMediaURL)
	strShortcut = strShortcut:gsub("\t",""):gsub("",function() return strURL end)	-- V1.2 fix
	SaveStringToFile(strShortcut,strMediaURL..strFileName)		-- Might overwrite an existing file
	return "Media\\URL\\"..strFileName
end -- function strMakeShortcutFile

function ptrMakeMediaRecord(strURL,strTitl,strFile)				-- Make Media record with URL Title linked to Shortcut file
	local isOK = false
	local ptrObje = fhNewItemPtr()
	ptrObje:MoveToFirstRecord("OBJE")
	if fhGetAppVersion() < 7 then
		while ptrObje:IsNotNull() do										-- Check for existing Media record for same Shortcut in GEDCOM 5.5
			if fhGetItemText(ptrObje,"~._FILE") == strFile
			and fhGetItemText(ptrObje,"~.TITL") == strTitl then
				return ptrObje
			end
			ptrObje:MoveNext()
		end
		ptrObje = fhCreateItem("OBJE")									-- Otherwise create new Media record in GEDCOM 5.5
		if ptrObje:IsNotNull() then
			for strTag, strVal in pairs ({ _KEYS="URL"; _FILE=strFile; TITL=strTitl; FORM="url"; }) do
				local ptrTag = fhCreateItem(strTag,ptrObje,true)
				if ptrTag:IsNotNull() then
					isOK = fhSetValueAsText(ptrTag,strVal)
					if not isOK then break end
				end
			end
		end
	else
		while ptrObje:IsNotNull() do										-- Check for existing Media record for same Shortcut in GEDCOM 5.5.1
			if fhGetItemText(ptrObje,"~.FILE") == strFile
			and fhGetItemText(ptrObje,"~.FILE.TITL") == strTitl then
				return ptrObje
			end
			ptrObje:MoveNext()
		end
		ptrObje = fhCreateItem("OBJE")									-- Otherwise create new Media record in GEDCOM 5.5.1
		if ptrObje:IsNotNull() then
			local ptrRoot = ptrObje:Clone()
			for _, strData in ipairs ({ "_KEYS:URL"; "FILE:"..strFile; "TITL:"..strTitl; "FORM:url"; }) do
				local strTag, strVal = strData:match("^(.+):(.+)$")
				local ptrTag = fhCreateItem(strTag,ptrRoot,true)
				if ptrTag:IsNotNull() then
					isOK = fhSetValueAsText(ptrTag,strVal)
					if not isOK then break end
				end
				if strTag == "FILE" then ptrRoot = ptrTag end
			end
		end
	end
	if not isOK then
		fhMessageBox("\nFailed to add Media record for URL Shortcut.\n")
		ptrObje:SetNull()
	end
	return ptrObje
end -- function ptrMakeMediaRecord

function ptrLinkMediaRecord(ptrRec,ptrObje)						-- Link Media record to Media tab of selected record
	local isOK = false
	local ptrLink = fhNewItemPtr()
	if ptrObje:IsNull() then return ptrLink end
	ptrLink:MoveTo(ptrRec,"~.OBJE")
	while ptrLink:IsNotNull() do										-- Check for existing Media link for same Shortcut
		if fhGetValueAsLink(ptrLink):IsSame(ptrObje) then
			return ptrLink
		end
		ptrLink:MoveNext("SAME_TAG")
	end
	ptrLink = fhCreateItem("OBJE",ptrRec,true)					-- Otherwise create new link to Media record
	if ptrLink:IsNotNull() then
		isOK = fhSetValueAsLink(ptrLink,ptrObje)
	end
	if not isOK then
		fhMessageBox("\nFailed to add Media tab link to Media record.\n")
		ptrLink:SetNull()
	end
	return ptrLink
end -- function ptrLinkMediaRecord

function strGetShortcutURL(strRec,strName)						-- Get Shortcut URL via user dialogue

	local strHelp =														-- Help and Advice message
	[[
	This adds a web page URL Shortcut to the Media tab of a chosen record.
	
	Enter desired web page URL and Title, then click 'Link Media URL' button.

	The Plugin then:
	  Adds a Shortcut file to the ....fh_data\Media\URL\ folder
	  Adds a Media record with chosen Title and URL, and Keyword = 'URL'
	  Adds a link to that Media record in the chosen record Media tab 

	To open Media URL click the triangular 'Open in Editor/Player' button.

	To undo changes use 'Edit > Undo Plugin Updates' before closing FH,
	& delete new shortcut files in ....fh_data\Media\URL\ folder.
	]]
	local strHead = strName.." to Media tab of '"..strRec.."'"
	local strURL, strTitl

	local function setArg(txtURL,txtTitl)							-- Action for btnLink, btnQuit, and Close window
		strURL = txtURL
		if #strURL < 9 then
			strURL = nil
		else
			strTitl = txtTitl
			if strTitl == "" then
				strTitl = strURL
			end
		end
		return iup.CLOSE
	end -- local function setArg

	-- Define IUP controls for user dialogue
	local labURL  = iup.label { Title="Enter web page URL: "; }
	local txtURL  = iup.text  { Expand="Yes"; Tip="Copy and Paste the URL here (http://...)"; }
	local boxURL  = iup.hbox  { Expand="Yes"; labURL; txtURL; }
	local labTitl = iup.label { Title="Enter the media Title:"; }
	local txtTitl = iup.text  { Expand="Yes"; Tip="If left blank, the Title defaults to URL"; }
	local boxTitl = iup.hbox  { Expand="Yes"; labTitl; txtTitl; }
	local btnLink = iup.button{ Expand="Yes"; Tip="Add Media URL Shortcut"; Title="Link Media URL" ; FgColor="0 128 0"; action=function() return setArg(txtURL.Value,txtTitl.Value) end; }
	local btnQuit = iup.button{ Expand="Yes"; Tip="Quit from this Plugin" ; Title="Quit the Plugin"; FgColor="255 0 0"; action=function() return setArg(" ") end; }
	local btnHelp = iup.button{ Expand="Yes"; Tip="Obtain Help and Advice"; Title="Help and Advice"; FgColor="0 128 0"; action=function() iup.Message(strName.." ~ Help and Advice",strHelp:gsub("\t","")) end; }
	local boxBtn  = iup.hbox  { Expand="Yes"; btnLink; btnQuit; btnHelp; }
	local labUndo = iup.label { Expand="Yes"; Title="Undo changes by using command 'Edit > Undo Plugin Updates' before closing Family Historian"; Alignment="Acenter"; }
	local boxAll  = iup.vbox  { Expand="Yes"; boxURL; boxTitl; boxBtn; labUndo; Homogeneous="Yes"; }
	local dialog  = iup.dialog{ Title=strHead; boxAll; RasterSize="700x280"; MinSize="700x280"; Padding="4x4"; Gap="9"; Margin="8x8"; MinBox="No"; MaxBox="No"; DefaultEnter=btnLink; DefaultEsc=btnQuit; close_cb=function() return setArg(" ") end; }
	dialog:show()
	iup.MainLoop()  
	return strURL, strTitl
end -- function strGetShortcutURL

function MainAction()
	local strName = "V1.3 "..fhGetContextInfo("CI_PLUGIN_NAME")
	local strMessage = "\nPlease select just one Individual, Family, or Source record.\n"
	if fhGetAppVersion() > 5 then									-- Cater for FH V6 Unicode and Place records
		fhSetStringEncoding("UTF-8")
		iup.SetGlobal("UTF8MODE","YES")
		iup.SetGlobal("UTF8MODE_FILE","NO")
		iup.SetGlobal("CUSTOMQUITMESSAGE","YES")					-- Needed for IUP 3.28
		strMessage = strMessage:gsub("or Source","Source, or Place")
	end	
	local arrRec = {}
	for intTag, strTag in ipairs ({"INDI","FAM","SOUR","_PLAC"}) do
		if fhGetAppVersion() > 5 or strTag ~= "_PLAC" then		-- Search for selected records
			arrRec = fhGetCurrentRecordSel(strTag)
			if #arrRec > 0 then break end
		end
	end
	if #arrRec == 1 then												-- Just one record selected, so get URL string
		local ptrRec = arrRec[1]
		local strRec = fhGetDisplayText(ptrRec):gsub("^%.%.%.of ","")
		local strURL, strTitl = strGetShortcutURL(strRec,strName)			-- Get URL and Title via user dialogue
		if strURL then
			local strFile = strMakeShortcutFile(strURL)						-- Make Shortcut file in \Media\URL\
			local ptrObje = ptrMakeMediaRecord(strURL,strTitl,strFile)	-- Make Media record for Shortcut
			local ptrLink = ptrLinkMediaRecord(ptrRec,ptrObje)				-- Link Media tab to Media record
			if ptrLink:IsNull() then
				error("\n\nPlugin failed to add Media URL.")
			end
		end
	else
		fhMessageBox(strName.."\n"..strMessage)					-- Zero or more than one record selected
	end
end -- function MainAction

if fhGetContextInfo("CI_APP_MODE") == "Project Mode" then
	MainAction()
else
	fhMessageBox("\nThis Plugin only works for Projects, not standalone Gedcom.\n")
end

Source:Add-Media-URL-Shortcut.fh_lua