User:JBHansson

From TNG_Wiki
Jump to: navigation, search

Jan B. Hansson

Jan B Hansson.png

Location:

  • Århus, Danmark

Education:

  • Carpenter
  • Bachelor of Science in Engineering
  • IT specialist and Developer

Occupation:

  • 1977-1988 COWIconsult - dev. of computer plotted highway and infrastructure design (incl 4 years stationed in Riyadh, Saudi Arabia)
  • 1988-2003 TDC Telecom Company - dev. of Geographical Information System
  • 2003-2008 CSC Consulting Group - dev. of Geographical Information System
  • 2008-2010 CSC ScandiHealth - dev. of Helth Care System

Programming Platform and Technology

  • 1977-1992 Pr1me (Fortran) and Intergraph
  • 1992-2010 Microsoft (VB, C#, Asp .NET, IIS, SQLserver)
  • 2011-2015 TNG, Legacy, Ancestry, MyHeritage
  • 2015-20xx TNG, Legacy (php, javascript, MySQL, CodeIgniter Framework)

My Genealogy Web Sites

TNG procedures in use

One way sync of genealogy person data from Legacy8 to TNG

In genealogy use only one way sync (mirror copy) from a master to a slave tool.

It is my experience never to do two way sync between tools from different makers. It will create merging problems. We can end up with different persons sharing the same personID and family relations.
As master tool, for genealogy person data creation and editing, I have selected and use Legacy8.
As slave tool, for online viewing the genealogy person data by one way sync from Legacy8, I have selected and use TNG 11.0.2
One way sync I do by Legacy8 exporting a GEDCOM (5.5/Legacy) file and in import the GEDCOM (5.5/Legacy) file into TNG.
In TNG I have some processing that must be done before and after importing the GEDCOM (5.5/Legacy) file:

Pre-Processing Before Importing into offline TNG:

  1. Manuel: Backup all TNG tables (excl. the users table) and Clear all person data in the Tree
  2. Run SQL: [1] Truncate the tables eventtypes, media, albumlinks, lbumplinks, albums, Associations


Manuel: Import the GEDCOM (5.5/Legacy) file

Post-Processing After Importing into offline TNG:

  1. Manuel: [1] Restore the tables media, albumlinks, albumplinks, albums, Associations
  2. Run SQL: Update people.branch based on Legacy8's Tags representing my family branches
  3. Run SQL: Check every person and all media (I am using the media.owner as branch) belongs to at least one the branches
  4. Run SQL: Update eventtypes.display to what shall be displayed (text, Go To links etc.)
  5. Run SQL: Update people.living - make sure the fictive ©TheHanssonTribe is alive - it is connected to all media to keep private
  6. Run SQL: Update people.living directly syncing the Living flag in the Legacy8 tblir.Living - this 1:1 sync will also handle last line
  7. Run SQL: Update notelinks.secret - make sure notes only displayed for ADMIN
  8. Manuel: Backup all TNG tables (excl. the users table)


Post-Processing to update online TNG:

  1. Manuel: FTP all the Backup TNG tables (excl. the users table) to online TNG where they are restored


Note[1]: My master for handling person data is Legacy8, but for handling media and users the master is TNG. Therefor it is important to a backup, truncate, restore at the right place in the listed process.

Analyzing the Living Flag / Death Facts distribution in tng_people table

Here are 4 sql scripts to analyze what are the distribution of the Living Flag / Death Facts in the tng_people table. The sum of the number of records the 4 script will return must be equal to the number of records in the table.


The four scripts can either be executed in a SQL client or they can (after replacing the * with column names to be displayed) be placed in 4 TNG reports

[1] Record list where Living Flag = 1 (alive) and Dead Facts is Not present
SELECT * FROM tng_people where living=1 and deathdate="" and deathplace="" and burialdate="" and burialplace="" order by LENGTH(personID),personID;

[2] Record list where Living Flag = 0 (dead) and Dead Facts is present
SELECT * FROM tng_people where living=0 and !(deathdate="" and deathplace="" and burialdate="" and burialplace="") order by LENGTH(personID),personID;

[3] Record list where Living Flag = 0 (dead) and Dead Facts is Not present
SELECT * FROM tng_people where living=0 and deathdate="" and deathplace="" and burialdate="" and burialplace="" order by LENGTH(personID),personID;

[4] Record list where Living Flag = 1 (alive) and Dead Facts is present
SELECT * FROM tng_people where living=1 and !(deathdate="" and deathplace="" and burialdate="" and burialplace="") order by LENGTH(personID),personID;

If all records in the table is found by [1] or [2] the Living Flag / Death Facts corresponds and OK.

If records is found in [3] / [4] as a solution to move these into healthy [1] or [2] it must be done in the master tool for editing. Here either correct the Living Flag or the Death Fact. In case the person is dead but you don't have any facts just write something (I use a Places reference - Practical Standard for 5-parts fixed formatted - so I will write _, _, _, _, Danmark which stands for place, parish, shire, county, country)

NB:
The 4 scripts does not account for Dead Facts can also be given via event types like: Alt Death, Alt Burial, Died before Eight

[2.1] When importing the GEDCOM file TNG can based on roles settings put deathdate="Y" - this script list the cases in [2] where this is added during import
SELECT * FROM tng_people where living=0 and deathdate="Y" order by LENGTH(personID),personID;

Area Search on Timeline

A tool for analyzing all people, with a event link to a selected area, presented on a timeline is useful for providing an overview and to find defects in genealogy data.


My version of SQL code that solves the problem is here in a version adapted to operate in a TNG Report. Remember to change the following values to your values:
1. Replace all my Table namepart tng1102 with your value
2. Replace all my Area of interest Haurum with your value
3. Replace all my Tree name TheHanssonTribe with your value
4. Introduce your extra Tree narrowing you have more than one tree
5. Living and Private handling not included in this version

Based on test this TNG Report takes 1-2 sec. for a result set of 400 people and 9-10 sec for a result set of 7000 people.

Demo of Area Search on Timeline

The current SQL version updated 12 jan. 2018:

SELECT 

concat('<B>', YEAR(IF(p.birthdatetr, p.birthdatetr, IF(p.altbirthdatetr, p.altbirthdatetr, IF(FamRoleHusband.marrdatetr, FamRoleHusband.marrdatetr, IF(FamRoleWife.marrdatetr, FamRoleWife.marrdatetr, IF(p.deathdatetr, p.deathdatetr, p.burialdatetr)))))),'</B>') AS Timeline, 

concat(concat('<H4><B>', p.firstname, ' ', p.lastname, ' ', p.suffix,'</B></H4>'),
'<a href=pedigree.php?personID=',p.personID,'&tree=TheHanssonTribe Target=\'_blank\'>',"Pedigree",'</a>',"    ",
'<a href=getperson.php?personID=',p.personID,'&tree=TheHanssonTribe Target=\'_blank\'>',p.personID,'</a>',"    ", 
'<a href=descend.php?personID=',p.personID,'&tree=TheHanssonTribe Target=\'_blank\'>',"Descend",'</a>',    
"\r\rC: ",IF (FamRoleChild.familyID IS NULL," ",Concat('<a href=familychart.php?familyID=',FamRoleChild.familyID,'&tree=TheHanssonTribe Target=\'_blank\'>',FamRoleChild.familyID,'</a>')),"    ",
" P: ",IF (FamRoleWife.familyID IS NULL, IF (FamRoleHusband.familyID IS NULL, " ", Concat('<a href=familychart.php?familyID=',FamRoleHusband.familyID,'&tree=TheHanssonTribe Target=\'_blank\'>',FamRoleHusband.familyID,'</a>')), Concat('<a href=familychart.php?familyID=',FamRoleWife.familyID,'&tree=TheHanssonTribe Target=\'_blank\'>',FamRoleWife.familyID,'</a>'))
) AS Person,

concat('<a href=getperson.php?personID=',concat_ws(" ",FamRoleHusband.wife, FamRoleWife.husband),'&tree=TheHanssonTribe Target=\'_blank\'>',concat_ws(" ",FamRoleHusband.wife, FamRoleWife.husband),'</a>')
 AS Partner, 

concat("B: ",birthdate,"\rC: ",altbirthdate,"\rM: ",concat_ws(" ",FamRoleHusband.marrdate, FamRoleWife.marrdate),"\rD: ",deathdate,"\rB: ",burialdate) AS Event_Date, 
concat("B: ",birthplace,"\rC: ",altbirthplace,"\rM: ",concat_ws(" ",FamRoleHusband.marrplace, FamRoleWife.marrplace),"\rD: ",deathplace,"\rB: ",burialplace) AS Event_Place, 
concat(p.gedcom,"\r\r",p.branch) AS Tree_Branch
FROM tng1111_people AS p 
LEFT JOIN tng1111_families AS groom ON (p.gedcom = groom.gedcom AND p.personID = groom.husband ) 
LEFT JOIN tng1111_families AS bride ON (p.gedcom = bride.gedcom AND p.personID = bride.wife )
LEFT JOIN tng1111_families AS FamRoleHusband ON (p.gedcom = FamRoleHusband.gedcom AND p.personID = FamRoleHusband.husband )                  
LEFT JOIN tng1111_families AS FamRoleWife ON (p.gedcom = FamRoleWife.gedcom AND p.personID = FamRoleWife.wife )                              
LEFT JOIN tng1111_children AS FamRoleChild ON (p.gedcom = FamRoleChild.gedcom AND p.personID = FamRoleChild.personID )                       
where p.gedcom = "TheHanssonTribe" AND groom.marrplace LIKE "%Danmark%" OR bride.marrplace LIKE "%Danmark%"

UNION DISTINCT 

SELECT 

concat('<B>', YEAR(IF(p.birthdatetr, p.birthdatetr, IF(p.altbirthdatetr, p.altbirthdatetr, IF(FamRoleHusband.marrdatetr, FamRoleHusband.marrdatetr, IF(FamRoleWife.marrdatetr, FamRoleWife.marrdatetr, IF(p.deathdatetr, p.deathdatetr, p.burialdatetr)))))),'</B>') AS Timeline, 

concat(concat('<H4><B>', p.firstname, ' ', p.lastname, ' ', p.suffix,'</B></H4>'),
'<a href=pedigree.php?personID=',p.personID,'&tree=TheHanssonTribe Target=\'_blank\'>',"Pedigree",'</a>',"    ",
'<a href=getperson.php?personID=',p.personID,'&tree=TheHanssonTribe Target=\'_blank\'>',p.personID,'</a>',"    ", 
'<a href=descend.php?personID=',p.personID,'&tree=TheHanssonTribe Target=\'_blank\'>',"Descend",'</a>',    
"\r\rC: ",IF (FamRoleChild.familyID IS NULL," ",Concat('<a href=familychart.php?familyID=',FamRoleChild.familyID,'&tree=TheHanssonTribe Target=\'_blank\'>',FamRoleChild.familyID,'</a>')),"    ",
" P: ",IF (FamRoleWife.familyID IS NULL, IF (FamRoleHusband.familyID IS NULL, " ", Concat('<a href=familychart.php?familyID=',FamRoleHusband.familyID,'&tree=TheHanssonTribe Target=\'_blank\'>',FamRoleHusband.familyID,'</a>')), Concat('<a href=familychart.php?familyID=',FamRoleWife.familyID,'&tree=TheHanssonTribe Target=\'_blank\'>',FamRoleWife.familyID,'</a>'))
) AS Person,

concat('<a href=getperson.php?personID=',concat_ws(" ",FamRoleHusband.wife, FamRoleWife.husband),'&tree=TheHanssonTribe Target=\'_blank\'>',concat_ws(" ",FamRoleHusband.wife, FamRoleWife.husband),'</a>')
 AS Partner, 

concat("B: ",birthdate,"\rC: ",altbirthdate,"\rM: ",concat_ws(" ",FamRoleHusband.marrdate, FamRoleWife.marrdate),"\rD: ",deathdate,"\rB: ",burialdate) AS Event_Date, 
concat("B: ",birthplace,"\rC: ",altbirthplace,"\rM: ",concat_ws(" ",FamRoleHusband.marrplace, FamRoleWife.marrplace),"\rD: ",deathplace,"\rB: ",burialplace) AS Event_Place, 
concat(p.gedcom,"\r\r",p.branch) AS Tree_Branch
FROM tng1111_people AS p 
LEFT JOIN tng1111_families AS FamRoleHusband ON (p.gedcom = FamRoleHusband.gedcom AND p.personID = FamRoleHusband.husband )                  
LEFT JOIN tng1111_families AS FamRoleWife ON (p.gedcom = FamRoleWife.gedcom AND p.personID = FamRoleWife.wife )                              
LEFT JOIN tng1111_children AS FamRoleChild ON (p.gedcom = FamRoleChild.gedcom AND p.personID = FamRoleChild.personID )                       
where p.gedcom = "TheHanssonTribe" AND p.birthplace LIKE "%Danmark%" OR p.altbirthplace LIKE "%Danmark%" OR p.deathplace LIKE "%Danmark%" OR p.burialplace LIKE "%Danmark%"

UNION DISTINCT

SELECT 

concat('<B>', YEAR(IF(p.birthdatetr, p.birthdatetr, IF(p.altbirthdatetr, p.altbirthdatetr, IF(FamRoleHusband.marrdatetr, FamRoleHusband.marrdatetr, IF(FamRoleWife.marrdatetr, FamRoleWife.marrdatetr, IF(p.deathdatetr, p.deathdatetr, p.burialdatetr)))))),'</B>') AS Timeline, 

concat(concat('<H4><B>', p.firstname, ' ', p.lastname, ' ', p.suffix,'</B></H4>'),
'<a href=pedigree.php?personID=',p.personID,'&tree=TheHanssonTribe Target=\'_blank\'>',"Pedigree",'</a>',"    ",
'<a href=getperson.php?personID=',p.personID,'&tree=TheHanssonTribe Target=\'_blank\'>',p.personID,'</a>',"    ", 
'<a href=descend.php?personID=',p.personID,'&tree=TheHanssonTribe Target=\'_blank\'>',"Descend",'</a>',    
"\r\rC: ",IF (FamRoleChild.familyID IS NULL," ",Concat('<a href=familychart.php?familyID=',FamRoleChild.familyID,'&tree=TheHanssonTribe Target=\'_blank\'>',FamRoleChild.familyID,'</a>')),"    ",
" P: ",IF (FamRoleWife.familyID IS NULL, IF (FamRoleHusband.familyID IS NULL, " ", Concat('<a href=familychart.php?familyID=',FamRoleHusband.familyID,'&tree=TheHanssonTribe Target=\'_blank\'>',FamRoleHusband.familyID,'</a>')), Concat('<a href=familychart.php?familyID=',FamRoleWife.familyID,'&tree=TheHanssonTribe Target=\'_blank\'>',FamRoleWife.familyID,'</a>'))
) AS Person,

concat('<a href=getperson.php?personID=',concat_ws(" ",FamRoleHusband.wife, FamRoleWife.husband),'&tree=TheHanssonTribe Target=\'_blank\'>',concat_ws(" ",FamRoleHusband.wife, FamRoleWife.husband),'</a>')
 AS Partner, 

concat("B: ",birthdate,"\rC: ",altbirthdate,"\rM: ",concat_ws(" ",FamRoleHusband.marrdate, FamRoleWife.marrdate),"\rD: ",deathdate,"\rB: ",burialdate) AS Event_Date, 
concat("B: ",birthplace,"\rC: ",altbirthplace,"\rM: ",concat_ws(" ",FamRoleHusband.marrplace, FamRoleWife.marrplace),"\rD: ",deathplace,"\rB: ",burialplace) AS Event_Place, 
concat(p.gedcom,"\r\r",p.branch) AS Tree_Branch
FROM tng1111_people AS p 
LEFT JOIN tng1111_events   AS persev ON (p.gedcom = persev.gedcom AND p.personID = persev.persfamID )   
LEFT JOIN tng1111_families AS FamRoleHusband ON (p.gedcom = FamRoleHusband.gedcom AND p.personID = FamRoleHusband.husband )                  
LEFT JOIN tng1111_families AS FamRoleWife ON (p.gedcom = FamRoleWife.gedcom AND p.personID = FamRoleWife.wife )                              
LEFT JOIN tng1111_children AS FamRoleChild ON (p.gedcom = FamRoleChild.gedcom AND p.personID = FamRoleChild.personID )                       
where p.gedcom = "TheHanssonTribe" AND persev.eventplace LIKE "%Danmark%"

ORDER BY Timeline, Event_Date


TNG customizations in use

Google Search and Go

The design goal of Google Search and Go is to add a search widget to the TNG Advanced Search page that lets your users use Google to search your own site, or any other sites that you identify through a configuration file. Also allows you to define links that your users can follow, and links that only administrators can follow. A co-development with Robin Richmond
NB: This MOD has been obsolite in TNG 11.0.0 replaced by Search this site and the original MOD was moved to Genealogy Toolbox

Visual Familytree Walking

The design goal of Visual Familytree Walking is to achieve that the user will have a very user-friendly opportunity with few mouse clicks quickly and visually to move around in the whole interrelated familytree (when there are no islands) - chart after chart in both ancestor and descendant direction and thereby be able to visit every person present in the familytree. A co-development with Chuck Filteau. Demo of Visual Familytree Walking


MakeUp Appearance

The design goal of MakeUp Appearance is to give the user the opportunity to customize the appearance of TNG to the user's taste. The appearence is achieved by applaying the style from the 15 TNG templates. Demo of MakeUp Appearance

TNG Mods I use

I have the following TNG mods installed on my web site:


TNG Extensions by Best Practice

Here follows some recipes on what I consider to be Best Practice in developing TNG Extensions. My expirence has been obtained over many years partly in professional IT companies and partly after retirement to play with changes and additions to TNG and working with the CodeIgniter3 Framework.

TNG Extensions by Design

The golden age for the lonely cowboy developer are definitely behind us. He was the only one who could overlook big spagiti programs, where one little change in one place could trigger errors elsewhere in the program. The software industry found a simple solution in the component industri. Divide and isolate the spagite programs into separate, isolated and independent components. Separate program parts in boxes with very little connection between boxes and allow only familiar and verifiable connections (interfaces) between boxes.

TNG Extensions Design Goals and Guidelines

Therefore following design goals and guidelines of TNG extensions can be listed:

  • Build extensions on the component idea to make TNG and component maintenance and upgrades more flawless
  • The component may well be aware of TNG inside content (user,language etc), but TNG should not be aware of the components internals.
  • Make as few and well-defined interface connections from component into TNG to make as little impact as possible.
Your Image Caption

On the figur to the right with the TNG root folder are outlined by a wall to separate what is Darrins TNG code zone and what belongs to the extension code zone. The goal is to keep the 2 code zones divided with as little impact on TNG as posible.

When building the MOD Google Search and Go in 2015, we had in tnguser2 discussions about best practice for MOD development. Could we use the TNG folder extensions in order not to mix own code with TNG code and it stranded on not have a solution to make it practical posible.

Since then I have created a proxy solution that works and have used it almost 2 years. It is described in section TNG extensions by Proxy.

To the repeated problems with TNG/MOD mixing of config and language code, I have found a practical solution for presented in the section TNG extensions by Config and section TNG extensions by Language

TNG Extensions by Proxy

Of the 3 proxy parts described here only _proxy_exec.php and _proxy_error.php has to be placed in the TNG root folder. Both their names start with underscores to make them surface the TNG files.

NB. After using the described proxy system on my website for almost 2 years to run multiple calls by proxy, I encountered one php feature PHP_SELF that will fail. A search shows that PHP_SELF is not used in TNG probably due to security reasons and because there exist other solutions - see https://www.dzhang.com/blog/2013/05/20/php_self-and-cross-site-scripting

_proxy_job.php

The short snippet code showen in the _proxy_job.php file is the job description included and used in the _proxy_exec.php file. The php begin tag are followed by 4 commented out lines containing the job instructions template to follow. Then comes 2 set of job descriptions that actuelly can be testet and they will result in the 2 links - the first will execute a PHP file located in a extensions subfolder and the second a PDF file as described is actuelly located in a path outside the TNG folder (placed in CodeIgniter folder structure). And I found 2 that are allowed by guest visitors. The proxy system will fail if the visitor is not matching the allow key. One nice safty thing is that the path and filename is not showen to the visitors address line.

https://xuri.dk/Genealogy/_proxy_exec.php?job=mb_008
https://xuri.dk/Genealogy/_proxy_exec.php?job=mbww2

<?php
// Proxy Job description - include ("extensions/T99/t99_proxy_job.php");
// $proxy_idx['x'] = "_proxy_exec.php?job=x";
// $proxy_fil['x'] = "path/filename";
// $proxy_key['x'] = "allow_admin|allow_user|allow_guest,filetype_php|filetype_html|filetype_pdf";

$proxy_job['mb_008'] = "_proxy_exec.php?job=mb_008";
$proxy_fil['mb_008'] = "../assets/tng/pdf/mbww2/008_mandem_bag_det_hele.pdf";
$proxy_key['mb_008'] = "allow_guest,filetype_pdf";

$proxy_job['mbww2'] = "_proxy_exec.php?job=mbww2";
$proxy_fil['mbww2'] = "extensions/T99/cmp_mbww2/t99_cmp_mbww2.php";
$proxy_key['mbww2'] = "allow_guest,filetype_php";
?>

_proxy_exec.php

The short but complete code in the _proxy_exec.php file is the driving force behind the entire proxy solution. The php begin tag are followed by 6 include files representing 6 components in subfolders of the extensions folder containing job instructions to be handled by the _proxy_exec.php

<?php
@include ("extensions/ADM/SAM/adm_sam_proxy_job.php");
@include ("extensions/T99/cmp_places/t99_proxy_job.php");
@include ("extensions/T99/cmp_ba1976/t99_proxy_job.php");
@include ("extensions/T99/cmp_mbww2/t99_proxy_job.php");
@include ("extensions/T99/cmp_portrait/t99_proxy_job.php");
@include ("extensions/T99/t99_proxy_job.php");
$task_job = (isset($_GET["job"])) ? $_GET["job"] : redirectTo("_proxy_error.php?code=401");
$task_fil = $proxy_fil[$task_job];
$task_key = $proxy_key[$task_job];
session_start();
if ( stripos($task_key, "allow_admin") !== false) if ($_SESSION['allow_admin']   == 0) redirectTo("login.php"); else  goto branch_filetype; 
if ( stripos($task_key, "allow_user" ) !== false) if ($_SESSION['allow_living']  == 0) redirectTo("login.php"); else  goto branch_filetype; 
if ( stripos($task_key, "allow_guest") !== false) goto branch_filetype; else redirectTo("_proxy_error.php?code=402");
branch_filetype:
if ( stripos($task_key, "filetype_php") !== false ) @include $task_fil;
if ( stripos($task_key, "filetype_html" ) !== false) redirectTo($task_fil);
if ( stripos($task_key, "filetype_pdf") !== false) {
    header('Content-type: application/pdf');
    header('Content-Disposition: inline; filename="' . basename($task_fil) . '"');
    header('Content-Transfer-Encoding: binary');
    header('Accept-Ranges: bytes');
    readfile($task_fil);
}
exit;
function redirectTo($url){header ("Location: " . $url);exit;}
?>

_proxy_error.php

Only a part of a big file with many error codes used to call with an error code it will handle redirection and showing message to the visitor.

<?php
    $code_index = (isset($_GET["code"])) ? $_GET["code"] : "100";
    // https://help.dreamhost.com/hc/en-us/articles/215840318-Custom-error-pages
    $page_redirected_from = $_SERVER['REQUEST_URI'];  // this is especially useful with error 404 to indicate the missing page.
    $server_url = "http://" . $_SERVER["SERVER_NAME"] . "/";
    //$redirect_url = $_SERVER["REDIRECT_URL"];
    //$redirect_url_array = parse_url($redirect_url);
    //$end_of_path = strrchr($redirect_url_array["path"], "/");
	$redirect_to = $explanation = $error_code = "";
//switch(getenv("REDIRECT_STATUS"))
switch($code_index)
{
	case 401:
	$error_code = "401 - Unauthorized";
	$explanation = "The request requires user authentication. The response MUST include a WWW-Authenticate header field (section 14.47) containing a challenge applicable to the requested resource. The client MAY repeat the request with a suitable Authorization header field (section 14.8). If the request already included Authorization credentials, then the 401 response indicates that authorization has been refused for those credentials. If the 401 response contains the same challenge as the prior response, and the user agent has already attempted authentication at least once, then the user SHOULD be presented the entity that was given in the response, since that entity might include relevant diagnostic information. HTTP access authentication is explained in HTTP Authentication: Basic and Digest Access Authentication
This section requires a password or is otherwise protected. If you feel you have reached this page in error, please return to the login page and try again, or contact the webmaster if you continue to have problems.";
	$redirect_to = "";
	break;
	case 403:
	$error_code = "403 - Forbidden";
	$explanation = "The server understood the request, but is refusing to fulfill it. Authorization will not help and the request SHOULD NOT be repeated. If the request method was not HEAD and the server wishes to make public why the request has not been fulfilled, it SHOULD describe the reason for the refusal in the entity. If the server does not wish to make this information available to the client, the status code 404 (Not Found) can be used instead.
This section requires a password or is otherwise protected. If you feel you have reached this page in error, please return to the login page and try again, or contact the webmaster if you continue to have problems.";
	$redirect_to = "";
	break;
	case 404:
	$error_code = "404 - Not Found";
	$explanation = "The server has not found anything matching the Request-URI. No indication is given of whether the condition is temporary or permanent. The 410 (Gone) status code SHOULD be used if the server knows, through some internally configurable mechanism, that an old resource is permanently unavailable and has no forwarding address. This status code is commonly used when the server does not wish to reveal exactly why the request has been refused, or when no other response is applicable.
The requested resource '" . $page_redirected_from . "' could not be found on this server. Please verify the address and try again.";
	$redirect_to = $server_url . "wiki" . $end_of_path;
	break;
	case 999:
	$error_code = "999 - Program part needs login";
	$explanation = "Program part needs login";
	$redirect_to = "login.php";
	break;
};
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<link rel="Shortcut Icon" href="/favicon.ico" type="image/x-icon" />
<?php
	if ($redirect_to != "")
	{
?>
	<meta http-equiv="Refresh" content="10; url='<?php print($redirect_to); ?>'">
<?php
	}
?>
	<title>Page not found: <?php print ($redirect_to); ?></title>
</head>
    <body>
        <h1>Error Code <?php print ($error_code); ?></h1>
        <p>The <a href="http://en.wikipedia.org/wiki/Uniform_resource_locator">URL</a> you requested was not found. <?PHP echo($explanation); ?></p>
        <p><strong>Did you mean to type <a href="<?php print ($redirect_to); ?>"><?php print ($redirect_to); ?></a>?</strong> You will be automatically redirected there in 10 seconds.</p>
        <p>You may also want to try starting from the home page: <a href="<?php print ($server_url); ?>"><?php print ($server_url); ?></a></p>
        <hr />
        <p><i>A project of <a href="<?php print ($server_url); ?>"><?php print ($server_url); ?></a>.</i></p>
    </body>
</html>
<?php
?>

TNG Extensions by Config

After reading this link I found the config way to use locally in extensions components with explanations why.

https://www.abeautifulsite.net/a-better-way-to-write-config-files-in-php

As an example are here the config file used in the SAM component

<?php
return [
    'sam_visitor_probe'   => 'RUNNING',		      // values are RUNNING or STOPPED
    'sam_visitor_debug'   => 'OFF',		      // values are ON or OFF
    'access_table'        => 'xuri_adm_sam_visitor',  // Visitor table 
    'request_table'       => 'xuri_adm_sam_request',  // Request table
    'sitestat_table'      => 'xuri_adm_sam_sitestat', // Site Stat table
    'SitePrefix'          => 'https://xuri.dk',	      // Site prefix added to link requests
    'save_request_blue'   => 'N',		      // values are Y or N
    'save_request_green'  => 'Y',		      // values are Y or N
    'save_request_grey'   => 'Y',		      // values are Y or N
    'save_request_red'    => 'Y',		      // values are Y or N
    'save_request_yellow' => 'N',		      // values are Y or N
    'incl_inview_blue'    => 'N',		      // values are Y or N
    'incl_inview_green'   => 'Y',		      // values are Y or N
    'incl_inview_grey'    => 'Y',	     	      // values are Y or N
    'incl_inview_red'     => 'N',		      // values are Y or N
    'incl_inview_yellow'  => 'N',		      // values are Y or N
    'fastest_access'      => 1.0,	      	      // seconds between accesses to be considered fast. 1.0 is faster than most indexers
    'rip_ban'             => 6,			      // number of fast clicks without a reset before banned
    'rip_warn'            => 3,          	      // number of fast clicks without a reset before a warning
    'rip_reset'           => 4,         	      // fast click reset time in seconds
    'rip_ban_time'        => 60,     		      // ban time in seconds
    'max_recent_hits'     => 30,		      // number of consecutive hits before a captcha challenge is initiated (set to 0 to disable)
    'reset_recent_hits'   => 3600 * 8,		      // # of seconds before a reset of the rip_challenge hit tracker (default 8 hours)
    'abuseipdb_appkey'    => 'qc..........0H1',       // Jan - get your personal key at abuseipdb.com
	'Website_owner'   => 'Jan B. Hansson',
	'Website_url'     => 'Xuri.dk',
	'TNG_Name'        => '&copy;TheHanssonTribe',
	'TNG_Type'        => 'open TNG',
	'GMT_offset_sec'  => 2 * 3600,		     // Denmark +2 (summer) +1 (winther) hours to GMT - https://greenwichmeantime.com/time-zone/
    'max_images_num'      => 28	   		     // nos of random images (images_[1] .... images_[max])
];
?>

Now I don not need TNG's config system inside the component and to change to an array is just easy to work and do many things with. As a standard in my php files the now start like this:

<?php
$SAM_CFG = include('extensions/ADM/SAM/adm_sam_visitor_config.php');
if ($SAM_CFG['sam_visitor_debug'] == "ON") {
	ini_set('error_reporting', E_ALL);
	ini_set('display_errors', 1);
}

and functions using config like this:

function viewConfig() {
   Global $SAM_CFG;

and debug like this:

// Send debug code to the Javascript console
function debug_to_console($data) {
	Global $SAM_CFG;
	if ($SAM_CFG['sam_visitor_debug'] != 'ON') return;
    if(is_array($data) || is_object($data))
		echo("<script>console.log('PHP: ".json_encode($data)."');</script>");
	else
		echo("<script>console.log('PHP: ".$data."');</script>");
}
?>

TNG Extensions by Language

Also for the language area, it is a good idea to be self-sufficient inside the closed component and not be dependent on the TNG language. I create own local language files for each language area corresponding to TNG's language files and then have a language switcher to select the current language file

<?php
//---------------------------------------------------------------------------
// A pre-condition for the parameter $session_language has got a value, is that
// the parent program that includes t99_language_switcher.php previously has
// includeret getlang.php - otherwise the default language will be selected
// ----------------------------------------------------------------------------->
switch ($session_language) {
    case "English-UTF8":
        include "extensions/ADM/SAM/lang/sam_lang_english_utf8.php";
        break;
    case "Danish-UTF8":
        include "extensions/ADM/SAM/lang/sam_lang_danish_utf8.php";
        break;
    case "Norwegian-UTF8":
        include "extensions/ADM/SAM/lang/sam_lang_norwegian_utf8.php";
        break;
    case "Swedish-UTF8":
        include "extensions/ADM/SAM/lang/sam_lang_swedish_utf8.php";
        break;
    default:
        include "extensions/T99/lang/t99_lang_english_utf8.php";
}
?>

TNG Extensions by Install

Following TNG Extensions Design Goals and Guidelines in the making of the SAM component, I have created a SAM sub-sub folder under the extensions folder containing in one place all off the component parts I can zip or copy install. In the setup folder are 3 sql scripts for creating the external sql tables and the 2 files _proxy_error.php and _proxy_exec.php to be copied (if they do not already exsist) to the TNG root folder. The last missing task is to make the connection into TNG code. Establish menus to call the proxy system with the right job argument. Finally also a README.txt file with installation description (a MOD is not yet produced). It is easy this way also when TNG upgrades it is all in a folder just to copy to the new version and connect the component.

SAM.png

TNG extensions by Example (SAM)

Site Access Manager (SAM) is my latest component build after the described guidelines. SAM's functionality is best described using the SAM report menu to run via the proxy system:

https://xuri.dk/Genealogy/_proxy_exec.php?job=sam_php_pdf