<?php
/*********************************************************************************
 *
 * TimeTrex is a Workforce Management program developed by
 * TimeTrex Software Inc. Copyright (C) 2003 - 2021 TimeTrex Software Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY TIMETREX, TIMETREX DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
 * details.
 *
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program; if not, see http://www.gnu.org/licenses or write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 *
 *
 * You can contact TimeTrex headquarters at Unit 22 - 2475 Dobbin Rd. Suite
 * #292 West Kelowna, BC V4T 2E9, Canada or at email address info@timetrex.com.
 *
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Powered by TimeTrex" logo. If the display of the logo is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by TimeTrex".
 *
 ********************************************************************************/


/**
 * Class PurgeDatabase
 */
class PurgeDatabase {
	static $parent_table_column_map = [
			'users'           => 'user_id',
			'report_schedule' => 'user_report_data_id',
	];

	static $parent_table_map_array = [
			'absence_policy'                           => [
					'company',
			],
			'accrual'                                  => [
					'users',
			],
			'accrual_balance'                          => [
					'users',
					//'accrual_policy'
					'accrual_policy_account',
			],
			'accrual_policy'                           => [
					'company',
			],
			'accrual_policy_account'                   => [
					'company',
			],
			'accrual_policy_milestone'                 => [
					'accrual_policy',
			],
			'area_policy'                              => [
					'company',
			],
			'area_policy_location'                     => [
					'area_policy',
			],
			//Can't automatically purge this table, as user_id is often NULL for company wide settings.
			//'bank_account' => array(
			//				'company',
			//				'users'
			//				),
			'branch'                                   => [
					'company',
			],
			'bread_crumb'                              => [
					'users',
			],
			'break_policy'                             => [
					'company',
			],
			'client'                                   => [
					'company',
			],
			'client_balance'                           => [
					'client',
			],
			'client_contact'                           => [
					'client',
			],
			'client_group'                             => [
					'company',
			],
			'client_payment'                           => [
					'client',
			],
			'legal_entity'                             => [
					'company',
			],
			'remittance_source_account'                => [
					'legal_entity',
			],
			'remittance_destination_account'           => [
					'users',
			],
			'payroll_remittance_agency'                => [
					'legal_entity',
			],
			'payroll_remittance_agency_event'          => [
					'payroll_remittance_agency',
			],
			'company_deduction'                        => [
					'company',
			],
			'company_deduction_pay_stub_entry_account' => [
					'company_deduction',
			],
			'company_generic_map'                      => [
					'company',
			],
			'company_user_count'                       => [
					'company',
			],
			'contributing_pay_code_policy'             => [
					'company',
			],
			'contributing_shift_policy'                => [
					'company',
			],
			'currency'                                 => [
					'company',
			],
			'currency_rate'                            => [
					'currency',
			],
			'department'                               => [
					'company',
			],
			'government_document'                      => [
					'users',
			],
			'document'                                 => [
					'company',
			],
			'document_attachment'                      => [
					'document',
			],
			'document_group'                           => [
					'company',
			],
			'document_revision'                        => [
					'document',
			],
			'exception'                                => [
					'exception_policy',
					'users',
					'punch',
					'punch_control',
			],
			'exception_policy'                         => [
					'exception_policy_control',
			],
			'exception_policy_control'                 => [
					'company',
			],
			'hierarchy_control'                        => [
					'company',
			],
			'hierarchy_level'                          => [
					'hierarchy_control',
					'users',
			],
			'hierarchy_object_type'                    => [
					'hierarchy_control',
			],
			'hierarchy_user'                           => [
					'hierarchy_control',
					'users',
			],
			'holiday_policy'                           => [
					'company',
			],
			'holiday_policy_recurring_holiday'         => [
					'holiday_policy',
					'recurring_holiday',
			],
			'holidays'                                 => [
					'holiday_policy',
			],
			'invoice'                                  => [
					'client',
			],
			'invoice_config'                           => [
					'company',
			],
			'invoice_district'                         => [
					'company',
			],
			'invoice_transaction'                      => [
					'client',
					'product', //Invoice payments are product_id = 0
					'invoice',
			],
			'job'                                      => [
					'company',
			],
			'job_group'                                => [
					'company',
			],
			'job_item'                                 => [
					'company',
			],
			'job_item_amendment'                       => [
					'job',
			],
			'job_item_group'                           => [
					'company',
			],
			'geo_fence'                                => [
					'company',
			],


			'qualification'       => [
					'company',
			],
			'qualification_group' => [
					'company',
			],
			'user_education'      => [
					'users',
			],
			'user_license'        => [
					'users',
			],
			'user_skill'          => [
					'users',
			],
			'user_language'       => [
					'users',
			],
			'user_membership'     => [
					'users',
			],
			'user_review_control' => [
					'users',
			],
			'user_review'         => [
					'user_review_control',
			],
			'kpi'                 => [
					'company',
			],
			'kpi_group'           => [
					'company',
			],
			'ethnic_group'        => [
					'company',
			],
			'user_contact'        => [
					'users',
			],
			'user_setting'        => [
					'users',
			],
			'company_setting'     => [
					'company',
			],


			'job_vacancy'                  => [
					'company',
			],
			'job_applicant'                => [
					'company',
			],
			'job_application'              => [
					'job_applicant',
					'job_vacancy',
			],
			'job_applicant_location'       => [
					'job_applicant',
			],
			'job_applicant_employment'     => [
					'job_applicant',
			],
			'job_applicant_reference'      => [
					'job_applicant',
			],
			'job_applicant_education'      => [
					'job_applicant',
			],
			'job_applicant_skill'          => [
					'job_applicant',
			],
			'job_applicant_language'       => [
					'job_applicant',
			],
			'job_applicant_membership'     => [
					'job_applicant',
			],
			'job_applicant_license'        => [
					'job_applicant',
			],
			'recruitment_portal_config'    => [
					'company',
			],
			'recruitment_portal_field_map' => [
					'company',
			],

			'expense_policy' => [
					'company',
			],
			'user_expense'   => [
					'users',
					'expense_policy',
			],

			'meal_policy'                         => [
					'company',
			],
			'message_recipient'                   => [
					'users',
					'message_sender',
			],
			'message_sender'                      => [
					'users',
					'message_control',
			],
			'custom_field'                         => [
					'company',
			],
			'over_time_policy'                    => [
					'company',
			],
			'pay_code'                            => [
					'company',
			],
			'pay_formula_policy'                  => [
					'company',
			],
			'pay_period'                          => [
					'company',
					'pay_period_schedule',
			],
			'pay_period_schedule'                 => [
					'company',
			],
			'pay_period_schedule_user'            => [
					'pay_period_schedule',
					'users',
			],
			'pay_period_time_sheet_verify'        => [
					'pay_period',
					'users',
			],
			'pay_stub'                            => [
					'pay_period',
					'users',
			],
			'pay_stub_amendment'                  => [
					'users',
			],
			'pay_stub_entry'                      => [
					'pay_stub',
			],
			'pay_stub_entry_account'              => [
					'company',
			],
			'pay_stub_transaction'                => [
					'pay_stub',
			],
			'pay_stub_entry_account_link'         => [
					'company',
			],
			'payment_gateway'                     => [
					'company',
			],
			'payment_gateway_credit_card_type'    => [
					'payment_gateway',
			],
			'payment_gateway_currency'            => [
					'payment_gateway',
			],
			'permission'                          => [
					'permission_control',
			],
			'permission_control'                  => [
					'company',
			],
			'permission_user'                     => [
					'permission_control',
					'users',
			],
			'policy_group'                        => [
					'company',
			],
			'policy_group_user'                   => [
					'policy_group',
					'users',
			],
			'premium_policy'                      => [
					'company',
			],
			'premium_policy_branch'               => [
					'premium_policy',
					'branch',
			],
			'premium_policy_department'           => [
					'premium_policy',
					'department',
			],
			'premium_policy_job'                  => [
					'premium_policy',
					'job',
			],
			'premium_policy_job_group'            => [
					'premium_policy',
					'job_group',
			],
			'premium_policy_job_item'             => [
					'premium_policy',
					'job_item',
			],
			'premium_policy_job_item_group'       => [
					'premium_policy',
					'job_item_group',
			],
			'product'                             => [
					'company',
			],
			'product_group'                       => [
					'company',
			],
			'product_price'                       => [
					'product',
			],
			'punch'                               => [
					'punch_control',
			],
			'punch_control'                       => [
					'users',
			],
			'recurring_holiday'                   => [
					'company',
			],
			'recurring_ps_amendment'              => [
					'company',
			],
			'recurring_ps_amendment_user'         => [
					'recurring_ps_amendment',
					'users',
			],
			'recurring_schedule_control'          => [
					'company',
			],
			'recurring_schedule_template'         => [
					'recurring_schedule_template_control',
			],
			'recurring_schedule_template_control' => [
					'company',
			],
			//'recurring_schedule_user'             => [
			//		'recurring_schedule_control',
			//		'users',
			//],
			'recurring_schedule'                  => [
					'recurring_schedule_control',
					'users',
					'company',
			],
			'regular_time_policy'                 => [
					'company',
			],
			'report_schedule'                     => [
					'user_report_data',
			],
			'request'                             => [
					'users',
			],
			'request_schedule'                    => [
					'request',
			],

			'roe'                            => [
					'users',
			],
			'round_interval_policy'          => [
					'company',
			],
			'schedule'                       => [
					'users',
					'company',
			],
			'schedule_policy'                => [
					'company',
			],
			'shipping_policy'                => [
					'company',
			],
			'shipping_policy_object'         => [
					'shipping_policy',
			],
			'shipping_table_rate'            => [
					'shipping_policy',
			],
			'station'                        => [
					'company',
			],
			'station_branch'                 => [
					'station',
					'branch',
			],
			'station_department'             => [
					'station',
					'department',
			],
			'station_exclude_user'           => [
					'station',
					'users',
			],
			'station_include_user'           => [
					'station',
					'users',
			],
			'station_user'                   => [
					'station',
					'users',
			],
			'station_user_group'             => [
					'station',
			],
			'system_log'                     => [
					'users',
			],
			'system_log_detail'              => [
					'system_log',
			],
			'tax_policy'                     => [
					'company',
			],
			'tax_policy_object'              => [
					'tax_policy',
			],
			'user_date_total'                => [
					'users',
			],
			'user_deduction'                 => [
					'users',
					'company_deduction',
			],
			'user_default'                   => [
					'company',
			],
			'user_default_company_deduction' => [
					'user_default',
					'company_deduction',
			],
			'user_default_preference_notification' => [
					'user_default',
			],
			//Can't automatically purge this table, as user_id is often NULL for company wide settings.
			//'user_generic_data' => array(
			//				'users',
			//				'company'
			//				),
			'user_generic_status'            => [
					'users',
			],
			'user_group'                     => [
					'company',
			],
			'user_identification'            => [
					'users',
			],
			'user_preference'                => [
					'users',
			],
			'user_preference_notification'   => [
					'users',
			],
			'notification'   => [
					'users',
			],
			'device_token'   => [
					'users',
			],
			//Can't automatically purge this table, as user_id is often NULL for company wide settings.
			//'user_report_data' => array(
			//				'company',
			//				'users'
			//				),
			'report_custom_column'           => [
					'company',
			],
			'user_title'                     => [
					'company',
			],
			'user_wage'                      => [
					'users',
			],
			'users'                          => [
					'company',
			],
			'wage_group'                     => [
					'company',
			],
	];

	/**
	 * @return bool
	 * @noinspection PhpMissingBreakStatementInspection
	 */
	static function Execute() {
		global $db;

		//Make array of tables to purge, and the timeperiod to purge them at.
		Debug::Text( 'Purging database tables: ' . TTDate::getDate( 'DATE+TIME', time() ), __FILE__, __LINE__, __METHOD__, 10 );
		$purge_tables = [
				'user_generic_status'                      => 2,
				'punch'                                    => 60, //Punch must come before punch_control
				'punch_control'                            => 60, //punch_control must come before user_date
				'user_date_total'                          => 60, //user_date_total must come before user_date
				'schedule'                                 => 60, //schedule must come before user_date
				'company'                                  => 120,
				'company_deduction'                        => 120,
				'company_deduction_pay_stub_entry_account' => 120,
				'company_generic_map'                      => 125, //Since we don't bother mapping to all possible other tables, this value should be higher than any other table.
				'company_user_count'                       => 120,
				'legal_entity'                             => 120,
				'remittance_source_account'                => 120,
				'remittance_destination_account'           => 120,
				'payroll_remittance_agency'                => 120,
				'payroll_remittance_agency_event'          => 120,
				'authentication'                           => 2, //Sessions.
				'hierarchy_user'                           => 45,
				'hierarchy_object_type'                    => 45,
				'hierarchy_level'                          => 45,
				'absence_policy'                           => 45,
				'accrual'                                  => 60,
				'accrual_balance'                          => 60, //Doesnt have updated_date column
				'accrual_policy'                           => 45,
				'accrual_policy_milestone'                 => 60,
				'accrual_policy_account'                   => 120,
				'authorizations'                           => 45, //Must go before requests.
				'bank_account'                             => 45,
				'branch'                                   => 45,
				'break_policy'                             => 45,
				'wage_group'                               => 45,
				'cron'                                     => 45,
				'currency'                                 => 120,
				'currency_rate'                            => 120,
				'contributing_pay_code_policy'             => 45,
				'contributing_shift_policy'                => 45,
				'department'                               => 45,
				'exception'                                => 45,
				'exception_policy'                         => 45,
				'exception_policy_control'                 => 45,
				'hierarchy_control'                        => 45,
				'hierarchy_tree'                           => 45,
				'hierarchy_share'                          => 45,
				'holiday_policy'                           => 45,
				'holiday_policy_recurring_holiday'         => 45,
				'holidays'                                 => 45,
				'meal_policy'                              => 45,
				'message'                                  => 45,
				'message_sender'                           => 45,
				'message_recipient'                        => 45,
				'message_control'                          => 45,
				'custom_field'                             => 45,
				'over_time_policy'                         => 45,
				'pay_code'                                 => 90,
				'pay_formula_policy'                       => 90,
				'pay_period'                               => 45,
				'pay_period_schedule'                      => 45,
				'pay_period_schedule_user'                 => 45,
				'pay_period_time_sheet_verify'             => 45,
				'pay_stub'                                 => 120,
				'pay_stub_amendment'                       => 120,
				'pay_stub_entry'                           => 120,
				'pay_stub_entry_account'                   => 120,
				'pay_stub_entry_account_link'              => 120,
				'pay_stub_transaction'                     => 120,
				'permission'                               => 45,
				'permission_control'                       => 45,
				'permission_user'                          => 45,
				'policy_group'                             => 45,
				'policy_group_user'                        => 45,
				'premium_policy'                           => 45,
				'premium_policy_branch'                    => 45,
				'premium_policy_department'                => 45,
				'recurring_holiday'                        => 45,
				'recurring_ps_amendment'                   => 45,
				'recurring_ps_amendment_user'              => 45,
				'recurring_schedule_control'               => 45,
				'recurring_schedule_template'              => 45,
				'recurring_schedule_template_control'      => 45,
				//'recurring_schedule_user'                  => 45,
				'regular_time_policy'                      => 45,
				'request'                                  => 45,
				'roe'                                      => 45,
				'round_interval_policy'                    => 45,
				'schedule_policy'                          => 45,
				'station'                                  => 45,
				'station_user'                             => 45,
				'station_branch'                           => 45,
				'station_department'                       => 45,
				'station_user_group'                       => 45,
				'station_include_user'                     => 45,
				'station_exclude_user'                     => 45,
				'user_deduction'                           => 120, //This is related to payroll, so keep as long as pay stubs exist.
				'user_default'                             => 45,
				'user_default_company_deduction'           => 45,
				'user_default_preference_notification'     => 45,
				'user_generic_data'                        => 45,
				'user_group'                               => 45,
				'user_group_tree'                          => 45,
				'user_identification'                      => 45,
				'user_preference'                          => 45,
				'user_preference_notification'             => 45,
				'user_title'                               => 45,
				'user_wage'                                => 120,
				'user_report_data'                         => 45,
				'user_setting'                             => 120,
				'company_setting'                          => 45,
				'users'                                    => 120,
				'recurring_schedule'                       => 0, //Delete these immediately.
				'bread_crumb'                              => 45,
				'system_log'                               => 45,
				'system_log_detail'                        => 45,
				'company_generic_tag_map'                  => 45,
				'company_generic_tag'                      => 45,
				'qualification'                            => 45,
				'user_education'                           => 45,
				'user_license'                             => 45,
				'user_skill'                               => 45,
				'user_language'                            => 45,
				'user_membership'                          => 45,
				'qualification_group'                      => 45,
				'qualification_group_tree'                 => 45,
				'kpi'                                      => 45,
				'kpi_group'                                => 45,
				'kpi_group_tree'                           => 45,
				'user_review'                              => 45,
				'user_review_control'                      => 45,
				'user_contact'                             => 45,
				'ethnic_group'                             => 45,
				'notification'                             => 21, //Keep notifications for three weeks.
				'device_token'                             => 45,
		];

		if ( getTTProductEdition() >= TT_PRODUCT_PROFESSIONAL ) {
			$purge_professional_tables = [
					'request_schedule'     => 45,
					'report_schedule'      => 45,
					'report_custom_column' => 45,
			];

			$purge_tables = array_merge( $purge_tables, $purge_professional_tables );
		}

		if ( getTTProductEdition() >= TT_PRODUCT_CORPORATE ) {
			$purge_corporate_tables = [
					'client'                           => 45,
					'client_contact'                   => 45,
					'client_group'                     => 45,
					'client_group_tree'                => 45,
					'client_payment'                   => 45,
					'client_balance'                   => 45,
					'premium_policy_job'               => 45,
					'premium_policy_job_group'         => 45,
					'premium_policy_job_item'          => 45,
					'premium_policy_job_item_group'    => 45,
					'area_policy'                      => 45,
					'area_policy_location'             => 45,
					'government_document'              => 120,
					'document'                         => 45,
					'document_attachment'              => 45,
					'document_group'                   => 45,
					'document_group_tree'              => 45,
					'document_revision'                => 45,
					'invoice'                          => 45,
					'invoice_config'                   => 45,
					'invoice_district'                 => 45,
					'invoice_transaction'              => 90,
					'job'                              => 45,
					'job_user_allow'                   => 45,
					'job_group'                        => 45,
					'job_group_tree'                   => 45,
					'job_item'                         => 45,
					'job_item_allow'                   => 45,
					'job_item_amendment'               => 45,
					'job_item_group'                   => 45,
					'job_item_group_tree'              => 45,
					'geo_fence'                        => 45,
					'payment_gateway'                  => 45,
					'payment_gateway_currency'         => 45,
					'payment_gateway_credit_card_type' => 45,
					'product'                          => 45,
					'product_group'                    => 45,
					'product_group_tree'               => 45,
					'product_price'                    => 45,
					'shipping_policy'                  => 45,
					'shipping_table_rate'              => 45,
					'shipping_policy_object'           => 45,
					'tax_policy'                       => 45,
					'tax_policy_object'                => 45,
			];

			$purge_tables = array_merge( $purge_tables, $purge_corporate_tables );
		}

		if ( getTTProductEdition() >= TT_PRODUCT_ENTERPRISE ) {
			$purge_enterprise_tables = [
					'job_vacancy'                  => 45,
					'job_applicant_location'       => 45,
					'job_applicant_employment'     => 45,
					'job_applicant_reference'      => 45,
					'job_applicant_education'      => 45,
					'job_applicant_skill'          => 45,
					'job_applicant_language'       => 45,
					'job_applicant_membership'     => 45,
					'job_applicant_license'        => 45,
					'job_applicant'                => 45,
					'job_application'              => 45,
					'recruitment_portal_config'    => 45,
					'recruitment_portal_field_map' => 45,
					'expense_policy'               => 45,
					'user_expense'                 => 45,
			];

			$purge_tables = array_merge( $purge_tables, $purge_enterprise_tables );
		}

		$current_tables = $db->MetaTables();
		$total_purged_rows = 0;

		if ( is_array( $purge_tables ) && is_array( $current_tables ) ) {
			foreach ( $purge_tables as $table => $expire_days ) {
				//For testing only, force expire_days to 0.
				//if ( PRODUCTION == FALSE ) {
				//	$expire_days = 0;
				//}

				$db->StartTrans(); //Try to keep the transactions as short lived as possible to avoid replication delays on the database.
				if ( in_array( $table, $current_tables ) ) {
					$query = [];
					switch ( $table ) {
						case 'user_generic_status':
							//Treat the user_generic_status table differently, as rows are never marked as deleted in it.
							//  Therefore use updated_date instead of deleted_date.
							$query[] = 'DELETE FROM ' . $table . ' WHERE updated_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) );
							break;
						case 'system_log':
							//Only delete system_log rows from deleted users, or deleted/cancelled companies
							$query[] = 'DELETE FROM ' . $table . ' as a USING users as b, company as c WHERE a.user_id = b.id AND b.company_id = c.id AND ( b.deleted = 1 OR c.deleted = 1 OR c.status_id = 30 ) AND ( a.date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ' AND b.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ' AND c.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ')';

							//Quite a few system_log rows are created by user_id=0 (system), they aren't shown on the audit log anyways and don't need to be kept around for long.
							//This also deletes logs that can't be matches to any user, or those that have already been deleted.
							//This includes log entries for the cron system.
							//$query[] = 'DELETE FROM '. $table .' WHERE id in ( select a.id from '. $table .' as a LEFT JOIN users as b ON a.user_id = b.id WHERE b.id is NULL AND ( a.date <= '. (time()-(86400*($expire_days))) .' ) )';
							//NOTE: Make sure NOT EXISTS is a strict join query without any other where clauses, as that can cause unintended results.
							$query[] = 'DELETE FROM ' . $table . ' as a WHERE a.date <= ' . ( time() - ( 86400 * ( ( $expire_days * 2 ) ) ) ) . ' AND NOT EXISTS ( select 1 from users as b WHERE a.user_id = b.id )';

							//Delete non-critical audit log entries that bloat the database
							// 45 Days
							//  - Station ( Allowed )

							// 180 Days
							//  - Authentication
							//  - UserDateTotal ( Notice - Recalculating )
							//  - Exception ( Notice - Emails )
							//  - ReportSchedule ( Notice - Emailing )
							//  - Punch ( Telephone Start records )

							// 1.5 Years
							//  - PayStubAmendment ( Edit )
							//  - PunchControl ( Add )
							//  - Punch ( Add )
							//  - Schedule ( Add )
							$query[] = 'DELETE FROM ' . $table . ' as a WHERE
								(
									a.date <= ' . ( time() - ( 86400 * ( ( $expire_days * 1 ) ) ) ) . '
									AND (
											( table_name = \'station\' AND action_id = 200 )
										)
								)
								OR
								(
									a.date <= ' . ( time() - ( 86400 * ( ( $expire_days * 4 ) ) ) ) . '
									AND (
											( table_name = \'authentication\' )
											OR
											( table_name = \'user_date_total\' AND action_id = 500 )
											OR
											( table_name = \'exception\' AND action_id = 500 )
											OR
											( table_name = \'report_schedule\' AND action_id = 500 )
											OR
											( table_name = \'punch\' AND ( action_id = 500 AND description LIKE \'Telephone Punch Start%\' ) )
										)
								)
								OR
								(
									a.date <= ' . ( time() - ( 86400 * ( ( $expire_days * 12 ) ) ) ) . '
									AND (
											( table_name = \'pay_stub_amendment\' AND action_id = 20 )
											OR
											( table_name = \'punch_control\' AND action_id = 10 )
											OR
											( table_name = \'punch\' AND action_id = 10 )
											OR
											( table_name = \'schedule\' AND action_id = 10 )
										)
								)
								';
							break;
						case 'system_log_detail':
							//Only delete system_log_detail rows when the corresponding system_log rows are already deleted
							//$query[] = 'DELETE FROM '. $table .' WHERE id in ( select a.id from '. $table .' as a LEFT JOIN system_log as b ON a.system_log_id = b.id WHERE b.id is NULL )';
							$query[] = 'DELETE FROM ' . $table . ' as a WHERE NOT EXISTS ( select 1 from system_log as b WHERE a.system_log_id = b.id )';
							break;
						case 'punch':
							//Delete punch rows from deleted users, or deleted companies
							$query[] = 'DELETE FROM ' . $table . ' as a USING punch_control as b, users as d, company as e WHERE a.punch_control_id = b.id AND b.user_id = d.id AND d.company_id = e.id AND ( a.deleted = 1 OR b.deleted = 1 OR d.deleted = 1 OR e.deleted = 1 ) AND ( a.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ' AND d.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ' AND e.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ')';
							break;
						case 'punch_control':
						case 'user_date_total':
						case 'schedule':
						case 'request':
							//Delete punch_control/user_date rows from deleted users, or deleted companies
							$query[] = 'DELETE FROM ' . $table . ' as a USING users as d, company as e WHERE a.user_id = d.id AND d.company_id = e.id AND ( a.deleted = 1 OR d.deleted = 1 OR e.deleted = 1 ) AND ( a.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ' AND d.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ' AND e.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ')';
							break;
						case 'exception':
							//Delete exception rows from deleted users, or deleted companies
							$query[] = 'DELETE FROM ' . $table . ' as a USING users as d, company as e WHERE a.user_id = d.id AND d.company_id = e.id AND ( a.deleted = 1 OR d.deleted = 1 OR e.deleted = 1 ) AND ( a.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ' AND d.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ' AND e.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ')';

							//Delete exception rows from terminated users after they have been terminated for 3x the regular expire length.
							//  This has to use updated_date rather than deleted_date in this case then.
							$query[] = 'DELETE FROM ' . $table . ' as a USING users as d, company as e WHERE a.user_id = d.id AND d.company_id = e.id AND ( d.status_id = 20 ) AND ( a.updated_date <= ' . ( time() - ( 86400 * ( $expire_days * 3 ) ) ) . ' AND d.updated_date <= ' . ( time() - ( 86400 * ( $expire_days * 3 ) ) ) . ' AND e.updated_date <= ' . ( time() - ( 86400 * ( $expire_days * 3 ) ) ) . ')';
							break;
						case 'user_identification':
							//Purge biometric data from terminated employees within 45 days (expire time) of their termination date.
							$query[] = 'UPDATE ' . $table . ' as a SET deleted = 1, updated_date = ' . time() . ', deleted_date = ' . time() . ', deleted_by = ' . $db->qstr( TTUUID::getZeroID() ) . ' FROM users as b WHERE a.user_id = b.id AND a.type_id in (20, 70, 71, 75, 76, 77, 78, 79, 80, 100, 101) AND b.status_id = 20 AND b.termination_date <= ' . $db->qstr( TTDate::getDBTimeStamp( ( time() - ( 86400 * ( $expire_days ) ) ) ) ) . ' AND a.deleted = 0 AND b.deleted = 0';
							//No break here, as it needs to continue through to message_recipient.
						case 'pay_period_time_sheet_verify':
						case 'message_sender':
						case 'message_recipient':
							//Delete rows from deleted users, or deleted companies
							$query[] = 'DELETE FROM ' . $table . ' as a USING users as d, company as e WHERE a.user_id = d.id AND d.company_id = e.id AND ( a.deleted = 1 OR d.deleted = 1 OR e.deleted = 1 ) AND ( a.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ' AND d.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ' AND e.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ')';
							break;
						case 'accrual_balance':
							//Delete rows from deleted users, or deleted companies. Accrual Balance table does not have deleted_date column.
							$query[] = 'DELETE FROM ' . $table . ' as a USING users as d, company as e WHERE a.user_id = d.id AND d.company_id = e.id AND ( a.deleted = 1 OR d.deleted = 1 OR e.deleted = 1 ) AND ( d.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ' AND e.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ')';
							break;
						case 'pay_stub_entry':
							//Only delete pay_stub_entry rows from deleted users, or deleted companies
							$query[] = 'DELETE FROM ' . $table . ' as a USING pay_stub as b WHERE a.pay_stub_id = b.id AND ( a.deleted = 1 OR b.deleted = 1 ) AND a.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) );
							break;
						case 'authorizations':
							//Only delete authorization rows from deleted requests.
							//  Make sure we try still try to purge old authorization type_ids (10,20,30), as they could be left laying around from long-time customers.
							$query[] = 'DELETE FROM ' . $table . ' as a USING request as b WHERE a.object_type_id in (10, 20, 30, 50, 1010, 1020, 1030, 1040, 1100) AND a.object_id = b.id AND ( b.deleted = 1 ) AND ( b.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ' AND a.updated_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ')';

							$query[] = 'DELETE FROM ' . $table . ' as a WHERE a.object_type_id in (10, 20, 30, 50, 1010, 1020, 1030, 1040, 1100) AND NOT EXISTS ( select 1 from request as b WHERE a.object_id = b.id)';
							$query[] = 'DELETE FROM ' . $table . ' as a WHERE a.object_type_id in (90) AND NOT EXISTS ( select 1 from pay_period_time_sheet_verify as b WHERE a.object_id = b.id)';

							if ( getTTProductEdition() >= TT_PRODUCT_ENTERPRISE ) {
								$query[] = 'DELETE FROM ' . $table . ' as a WHERE a.object_type_id in (200) AND NOT EXISTS ( select 1 from user_expense as b WHERE a.object_id = b.id)';
							}
							break;
						case 'notification':
							$query[] = 'DELETE FROM ' . $table . ' WHERE deleted = 1 AND deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) );

							//Delete *non-deleted* read notifications after $expire_days * 1, and unread notifications after $expire_days * 2. To prevent them from piling up especially if the user hasn't logged in for a while.
							//  Make sure effective date is before $expire_days as well, so the user at least has a chance to read a future dated notification.
							//  Since these are non-deleted records, use updated_date instead of deleted_date.
							$query[] = 'DELETE FROM ' . $table . ' WHERE deleted = 0 AND effective_date <= ' . $db->qstr( TTDate::getDBTimeStamp( ( time() - ( 86400 * ( $expire_days ) ) ) ) ) .' AND ( ( status_id = 10 AND updated_date <= ' . ( time() - ( 86400 * ( $expire_days * 2 ) ) ) .' ) OR ( status_id = 20 AND updated_date <= ' . ( time() - ( 86400 * ( $expire_days * 1 ) ) ) .' ) )'; //10=UnRead, 20=Read
							break;
						case 'station':
							//Delete stations that haven't been used (allowed_date) or updated in over two years. Only consider PC/WirelessWeb stations types though.
							//Problem is when a station is created, a punch may be assigned to it, but the allowed_date is update on the wildcard entry instead.
							//  Use updated_date instead of deleted_date here as they records haven't been deleted yet.
							//$query[] = 'DELETE FROM '. $table .' as a WHERE a.type_id in (10, 25) AND a.deleted = 0 AND ( lower(a.station_id) != \'any\' AND lower(a.source) != \'any\' ) AND ( a.allowed_date is NULL OR a.allowed_date <= '. (time()-(86400*(730))) .') AND ( a.updated_by is NULL AND a.updated_date <= '. (time()-(86400*($expire_days))) .')'; //This will delete active stations. DO NOT USE.
							$query[] = 'DELETE FROM ' . $table . ' as a WHERE a.type_id in (10, 25) AND ( lower(a.station_id) != \'any\' AND lower(a.source) != \'any\' ) AND NOT EXISTS ( select 1 from punch as b WHERE a.id = b.station_id ) AND ( a.updated_by is NULL AND a.updated_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ' ) AND a.deleted = 0';

							//Delete station rows from deleted/cancelled companies
							$query[] = 'DELETE FROM ' . $table . ' as a USING company as e WHERE a.company_id = e.id AND ( a.deleted = 1 OR e.deleted = 1 OR e.status_id = 30 ) AND ( a.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ' AND e.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ')';

							//Disable iButton/Fingerprint/Barcode stations that have never been used.
							//  Use updated_date instead of deleted_date here as they records haven't been deleted yet.
							$query[] = 'UPDATE ' . $table . ' SET status_id = 10 WHERE type_id in (30, 40, 50) AND status_id = 20 AND allowed_date is NULL AND updated_date <= ' . ( time() - ( 86400 * ( 120 ) ) ) . ' AND ( deleted = 0 )';
							break;
						case 'permission_control':
							// Use updated_date instead of deleted_date only for table "a." as we are deleting records where the company is deleted and not where the record itself may be deleted.
							$query[] = 'DELETE FROM ' . $table . ' as a USING company as c WHERE a.company_id = c.id AND ( c.deleted = 1 ) AND ( a.updated_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ' AND c.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ')';
							break;
						case 'permission':
							$query[] = 'DELETE FROM ' . $table . ' WHERE id in ( select a.id from ' . $table . ' as a LEFT JOIN permission_control as b ON a.permission_control_id = b.id WHERE b.id is NULL )';
							break;
						case 'message_control':
							$query[] = 'DELETE FROM ' . $table . ' WHERE id in ( select a.id from ' . $table . ' as a LEFT JOIN message_sender as b ON a.id = b.message_control_id WHERE b.id is NULL )';
							break;
						case 'bank_account':
						case 'user_generic_data':
						case 'user_report_data':
							//Delete all rows that are already deleted, since this isn't done in the DEFAULT case statement below
							$query[] = 'DELETE FROM ' . $table . ' WHERE deleted = 1 AND deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) );

							//user_id column can be NULL for company wide data, make sure we leave that alone.
							$query[] = 'DELETE FROM ' . $table . ' as a USING company as b WHERE a.company_id = b.id AND ( b.deleted = 1 AND b.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ')';
							$query[] = 'DELETE FROM ' . $table . ' as a USING users as b WHERE a.user_id = b.id AND ( b.deleted = 1 AND b.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ')';

							//Delete rows where the parent table rows are already deleted.
							$query[] = 'DELETE FROM ' . $table . ' as a WHERE NOT EXISTS ( select 1 from company as b WHERE a.company_id = b.id )';

							//bank_account can have user_id is NULL or user_id = 0, we don't want to purge those records in either case.
							$query[] = 'DELETE FROM ' . $table . ' as a WHERE ( a.user_id is NOT NULL AND a.user_id != \'' . TTUUID::getZeroID() . '\' ) AND NOT EXISTS ( select 1 from users as b WHERE a.user_id = b.id )';
							break;
						case 'user_group_tree':
						case 'document_group_tree':
						case 'client_group_tree':
						case 'job_group_tree':
						case 'job_item_group_tree':
						case 'product_group_tree':
						case 'qualification_group_tree':
						case 'kpi_group_tree':
							$parent_table = str_replace( '_tree', '', $table );
							$query[] = 'DELETE FROM ' . $table . ' as a WHERE NOT EXISTS ( select 1 from ' . $parent_table . ' as b WHERE a.object_id = b.id)';
							break;
						case 'authentication':
							$query[] = 'DELETE FROM ' . $table . ' as a USING users as b WHERE a.object_id = b.id AND a.type_id NOT IN (100,110) AND ( b.deleted = 1 AND b.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ')';
							break;
						case 'company_generic_tag_map':
							//Tag mapping is specific to almost every other major table, so need to handle it one at a time.
							$table_arr = $db->MetaTables(); //Get list of tables, so we don't cause SQL errors if the table doesn't exist.

							$cgtf = TTnew( 'CompanyGenericTagFactory' ); /** @var CompanyGenericTagFactory $cgtf */
							$object_type_arr = $cgtf->getOptions( 'object_type' );
							foreach ( $object_type_arr as $object_type_id => $object_type ) {
								if ( in_array( $object_type, $table_arr ) && isset( $purge_tables[$object_type] ) ) {
									$query[] = 'DELETE FROM ' . $table . ' as a WHERE a.object_type_id = ' . $object_type_id . ' AND NOT EXISTS ( select 1 from ' . $object_type . ' as b WHERE a.object_id = b.id)';
								} else {
									Debug::Text( '  WARNING: Table does not exist or is not in the purge tables array: ' . $object_type, __FILE__, __LINE__, __METHOD__, 10 );
								}
							}
							unset( $cgtf, $object_type_arr, $object_type, $table_arr );
							break;
						case 'company_generic_tag':
							$query[] = 'DELETE FROM ' . $table . ' as a WHERE NOT EXISTS ( select 1 from company_generic_tag_map as b WHERE a.id = b.tag_id)';
							break;
						//Tables that don't require custom queries, but don't have a deleted/updated_date column.
						case 'company_user_count':
						case 'permission_user':
						case 'user_default_company_deduction':
						//case 'recurring_schedule_user':
						case 'recurring_ps_amendment_user':
						case 'hierarchy_user':
						case 'hierarchy_object_type':
						case 'company_deduction_pay_stub_entry_account':
						case 'pay_period_schedule_user':
						case 'bread_crumb':
						case 'holiday_policy_recurring_holiday':
						case 'station_branch':
						case 'station_department':
						case 'station_user_group':
						case 'station_include_user':
						case 'station_exclude_user':
						case 'policy_group_user':
						case 'premium_policy_branch':
						case 'premium_policy_department':
						case 'premium_policy_job':
						case 'premium_policy_job_group':
						case 'premium_policy_job_item':
						case 'premium_policy_job_item_group':
						case 'client_balance':
						case 'tax_policy_object':
						case 'area_policy_location':
						case 'shipping_policy_object':
						case 'payment_gateway_currency':
						case 'payment_gateway_credit_card_type':
						case 'recruitment_portal_field_map':
							break;
						//Purge old tables from previous versions.
						case 'message':
						case 'hierarchy_tree':
						case 'hierarchy_share':
						case 'station_user':
						case 'job_user_allow':
						case 'job_item_allow':
							if ( version_compare( APPLICATION_VERSION, '3.5.0', '>=' ) ) {
								$query[] = 'DELETE FROM ' . $table;
							}
							break;
						default:
							Debug::Text( 'Default Query... Table: ' . $table, __FILE__, __LINE__, __METHOD__, 10 );
							$query[] = 'DELETE FROM ' . $table . ' WHERE deleted = 1 AND deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) );
							break;
					}

					//Handle orphaned data as well, based on the parent table.
					if ( isset( self::$parent_table_map_array[$table] ) ) {
						foreach ( self::$parent_table_map_array[$table] as $parent_table ) {
							if ( isset( self::$parent_table_column_map[$parent_table] ) ) {
								$parent_table_column = self::$parent_table_column_map[$parent_table];
							} else {
								$parent_table_column = $parent_table . '_id';
							}
							Debug::Text( 'Parent Table: ' . $parent_table . ' Parent Table Column: ' . $parent_table_column, __FILE__, __LINE__, __METHOD__, 10 );

							//Skip some tables without deleted columns.
							// remittance_source_account can be assigned to 'ANY' legal entity (Not Exists UUID), so make sure we don't purge data in it.
							if ( !in_array( $table, [ 'remittance_source_account', 'bank_account', 'user_generic_data', 'user_report_data', 'system_log', 'system_log_detail', 'authorizations' ] ) ) {
								//Delete rows where the parent table rows are already marked as deleted.
								$query[] = 'DELETE FROM ' . $table . ' as a USING ' . $parent_table . ' as b WHERE a.' . $parent_table_column . ' = b.id AND ( b.deleted = 1 AND b.deleted_date <= ' . ( time() - ( 86400 * ( $expire_days ) ) ) . ')';

								//Delete rows where the parent table rows are already deleted.
								//Keep records where ID = 0 or NULL as those can still be valid in some cases.
								$query[] = 'DELETE FROM ' . $table . ' as a WHERE a.' . $parent_table_column . ' != \'' . TTUUID::getZeroID() . '\' AND a.' . $parent_table_column . ' != \'' . TTUUID::getNotExistID() . '\' AND a.' . $parent_table_column . ' is NOT NULL AND NOT EXISTS ( SELECT 1 FROM ' . $parent_table . ' as b WHERE a.' . $parent_table_column . ' = b.id )';
							}

							unset( $parent_table_column, $parent_table );
						}
					}

					//FIXME: With new punch method in v3.0 add query to make sure orphaned punches without punch_control rows are cleaned out
					//select a.id, a.deleted, b.id, b.deleted from punch as a LEFT JOIN punch_control as b ON (a.punch_control_id = b.id) WHERE b.id is NULL AND a.deleted = 0;
					if ( isset( $query ) && is_array( $query ) ) {
						$i = 0;
						foreach ( $query as $q ) {
							//echo "Query: ". $q ."\n"; //Help with debugging SQL syntax errors.
							$db->Execute( $q );
							Debug::Text( 'Query: ' . $q, __FILE__, __LINE__, __METHOD__, 10 );
							Debug::Text( 'Table found for purging: ' . $table . '(' . $i . ') Expire Days: ' . $expire_days . ' Purged Rows: ' . $db->Affected_Rows(), __FILE__, __LINE__, __METHOD__, 10 );
							$total_purged_rows += $db->Affected_Rows();
							$i++;
						}
					}
					unset( $query );
				} else {
					Debug::Text( 'Table not found for purging: ' . $table, __FILE__, __LINE__, __METHOD__, 10 );
				}

				//$db->FailTrans();
				$db->CompleteTrans();
			}

			//
			//Purge saved punch images.
			//
			if ( getTTProductEdition() >= TT_PRODUCT_PROFESSIONAL ) {
				//This doesn't need to be wrapped in a transaction, and can take quite a while due to disk I/O. Doing so can harm the database performance, especially with replication.
				Debug::Text( 'Purging Punch Images...', __FILE__, __LINE__, __METHOD__, 10 );

				$plf = new PunchListFactory();
				$total_records = -1;
				$x = 0;
				$i = 0;
				while ( $total_records == -1 || ( $total_records > 0 && $x <= 250 ) ) { //Limit to 10 batches in total.
					$db->StartTrans();                                                   //Try to keep the transactions as short lived as possible to avoid replication delays on the database.

					$plf->getByHasImageAndCreatedDate( true, ( time() - ( 86400 * 120 ) ), 1000 ); //Expire Days: 120 -- Limit to 500 at a time, as its mostly the file I/O that takes the time here.
					$total_records = $plf->getRecordCount();
					Debug::text( 'Batch: ' . $x . ' Punches with images older than X days: ' . $plf->getRecordCount(), __FILE__, __LINE__, __METHOD__, 10 );
					if ( $plf->getRecordCount() > 0 ) {
						foreach ( $plf as $p_obj ) {
							Debug::text( '  Punch ID: ' . $p_obj->getID() . ' Date: ' . TTDate::getDate( 'DATE+TIME', $p_obj->getTimeStamp() ) . ' Image File Name: ' . $p_obj->getImageFileName(), __FILE__, __LINE__, __METHOD__, 10 );
							$query = 'UPDATE ' . $plf->getTable() . ' SET has_image = 0 WHERE id = \'' . TTUUID::castUUID( $p_obj->getID() ) . '\'';
							if ( $plf->ExecuteSQL( $query ) !== false ) {
								$p_obj->cleanStoragePath();
							} else {
								Debug::text( '  ERROR: Update query to purge has_image failed... ID: ' . $p_obj->getID(), __FILE__, __LINE__, __METHOD__, 10 );
							}
							$i++;
						}
					}

					//$db->FailTrans();
					$db->CompleteTrans();

					$x++;
				}
				Debug::text( '  Deleted Punch Images: ' . $i, __FILE__, __LINE__, __METHOD__, 10 );
				unset( $plf, $p_obj, $total_records, $x, $i, $query );
			}

			//Since deleting a lot of records especially in system_log can cause large planning times of queries that select from that table. This manifested itself in the Audit tab taking 2-3 seconds to load.
			// Therefore force a regular vacuum to run on the entire database once we are done. We could also tune the autovacuum parameters more for these tables to be extra sure.
			Debug::text( '  Start VACUUM...', __FILE__, __LINE__, __METHOD__, 10 );
			$db->Execute( 'VACUUM ANALYZE' );
			Debug::text( '  Done VACUUM...', __FILE__, __LINE__, __METHOD__, 10 );
		}
		unset( $purge_tables, $current_tables, $query );
		Debug::Text( 'Purging database tables complete: ' . TTDate::getDate( 'DATE+TIME', time() ) .' Total Purged Rows: '. $total_purged_rows, __FILE__, __LINE__, __METHOD__, 10 );

		return true;
	}

	/** @noinspection PhpUndefinedFunctionInspection */
	static function getParentTableMap() {
		global $db;

		//Get all tables in a database and their ID columns to aid in creating export/import mapping
		$exclude_columns = [ 'id', 'type_id', 'status_id', 'length_of_service_unit_id', 'apply_frequency_id', 'category_id', 'custom_field', 'ibutton_id', 'manual_id', 'exclusive_id', 'session_id', 'cc_type_id', 'originator_id', 'data_center_id', 'product_edition_id', 'calculation_id', 'severity_id', 'email_notification_id', 'default_schedule_status_id', 'phone_id', 'sex_id' ];

		$table_name_map = [];
		$table_name_map['user'] = 'users';

		$dict = NewDataDictionary( $db );
		$tables = $dict->MetaTables();
		sort( $tables );
		$map = [];
		foreach ( $tables as $table ) {
			$columns = $dict->MetaColumns( $table );

			foreach ( $columns as $column_table ) {
				$column_name = $column_table->name;
				if ( !in_array( $column_name, $exclude_columns ) && stristr( $column_name, '_id' ) ) {
					//Find out where the column maps too.
					$tmp_table_name = str_replace( '_id', '', $column_name );
					if ( isset( $table_name_map[$tmp_table_name] ) ) {
						$tmp_table_name = $table_name_map[$tmp_table_name];
					}

					if ( in_array( $tmp_table_name, $tables ) ) {
						//Found destination table.
						//$out .= $table . ', '. $column_name .', '. $tmp_table_name .', id'."\n";
						//$test[$tmp_table_name][] = $table;
						$map[$table][] = $tmp_table_name;
					} else {
						echo "UNABLE TO FIND DESTINATION TABLE FOR: Table: " . $table . " Column: " . $column_name . "<br>\n";
					}
				}
			}
		}
		//echo $out;
		//var_dump($test);
		//asort($map);
		foreach ( $map as $tmp_key => $tmp_val ) {
			echo "'$tmp_key' => array(\n\t\t\t\t'" . implode( "',\n\t\t\t\t'", $tmp_val ) . "'\n\t\t\t\t), \n";
			//echo "'$tmp_key' => '$tmp_val', \n";
		}
	}
}


?>