<?php
/*********************************************************************************
 * TimeTrex is a Payroll and Time Management program developed by
 * TimeTrex Payroll Services Copyright (C) 2003 - 2011 TimeTrex Payroll Services.
 *
 * 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 Westbank, 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".
 ********************************************************************************/
/*
 * $Revision: 3106 $
 * $Id: LogFactory.class.php 3106 2009-11-20 20:01:46Z ipso $
 * $Date: 2009-11-20 12:01:46 -0800 (Fri, 20 Nov 2009) $
 */

/**
 * @package Core
 */
class LogDetailFactory extends Factory {
	protected $table = 'system_log_detail';
	protected $pk_sequence_name = 'system_log_detail_id_seq'; //PK Sequence name

	function getSystemLog() {
		return $this->data['system_log_id'];
	}
	function setSystemLog($id) {
		$id = trim($id);

		//Allow NULL ids.
		if ( $id == '' OR $id == NULL ) {
			$id = 0;
		}

		$llf = new LogListFactory();

		if ( $id == 0
				OR $this->Validator->isResultSetWithRows(	'user',
															$llf->getByID($id),
															TTi18n::gettext('System log is invalid')
															) ) {
			$this->data['system_log_id'] = $id;

			return TRUE;
		}

		return FALSE;
	}

	function getField() {
		if ( isset($this->data['field']) ) {
			return $this->data['field'];
		}

		return FALSE;
	}
	function setField($value) {
		$value = trim($value);

		if (	$this->Validator->isString(		'field',
												$value,
												TTi18n::gettext('Field is invalid'))
			) {
			$this->data['field'] = $id;

			return TRUE;
		}

		return FALSE;
	}

	function getOldValue() {
		if ( isset($this->data['old_value']) ) {
			return $this->data['old_value'];
		}

		return FALSE;
	}
	function setOldValue($text) {
		$text = trim($text);

		if (
				$this->Validator->isLength(		'old_value',
												$text,
												TTi18n::gettext('Old value is invalid'),
												0,
												1024)

			) {
			$this->data['old_value'] = $text;

			return TRUE;
		}

		return FALSE;
	}

	function getNewValue() {
		if ( isset($this->data['new_value']) ) {
			return $this->data['new_value'];
		}

		return FALSE;
	}
	function setNewValue($text) {
		$text = trim($text);

		if (
				$this->Validator->isLength(		'new_value',
												$text,
												TTi18n::gettext('New value is invalid'),
												0,
												1024)

			) {
			$this->data['new_value'] = $text;

			return TRUE;
		}

		return FALSE;
	}

	function normalizeField( $obj, $field ) {
		$retval = str_replace('_id', '', $field);

		return $retval;
	}

	function getDisplayField( $obj ) {
		$class = get_class($obj);
		$field = $this->getField();

		if ( is_object( $obj ) ) {
			//Handle global fields
			$columns = array();
			$global_columns = array(
							'user_id' => TTi18n::getText('Employee'),
							'currency_id' => TTi18n::getText('Currency'),
							'branch_id' => TTi18n::getText('Branch'),
							'department_id' => TTi18n::getText('Department'),
							'job_id' => TTi18n::getText('Job'),
							'job_item_id' => TTi18n::getText('Task'),
							'exception_policy_control_id' => TTi18n::getText('Exception Policy'),
							'accrual_policy_id' => TTi18n::getText('Accrual Policy'),
							'absence_policy_id' => TTi18n::getText('Absence Policy'),
							'round_interval_policy_id' => TTi18n::getText('Rounding Policy'),
							'pay_stub_entry_account_id' => TTi18n::getText('Pay Stub Account'),
							'wage_group_id' => TTi18n::getText('Wage Group'),
							'company_deduction_id' => TTi18n::getText('Tax / Deduction'),

							'client_id' => TTi18n::getText('Client'),
							'product_id' => TTi18n::getText('Product'),
							'invoice_district_id' => TTi18n::getText('District'),
							'total_time' => TTi18n::getText('Total Time'),
							);

			//Handle class specific fields.
			switch ( $class ) {
				case 'StationFactory':
				case 'StationListFactory':
					$columns = array(
									'partial_push_frequency' => TTi18n::getText('Partial Push Frequency'),
									'push_frequency' => TTi18n::getText('Push Frequency'),
									'poll_frequency' => TTi18n::getText('Poll Frequency'),
									'enable_auto_punch_status' => TTi18n::getText('Enable Auto Punch Status'),
									);
					break;
				case 'MealPolicyFactory':
				case 'MealPolicyListFactory':
				case 'BreakPolicyFactory':
				case 'BreakPolicyListFactory':
					$columns = array(
									'start_window' => TTi18n::getText('Start Window'),
									'window_length' => TTi18n::getText('Window Length'),
									'minimum_punch_time' => TTi18n::getText('Minimum Punch Time'),
									'maximum_punch_time' => TTi18n::getText('Maximum Punch Time'),
									);
					break;
				case 'AccrualPolicyFactory':
				case 'AccrualPolicyListFactory':
					$columns = array(
									'enable_pay_stub_balance_display' => TTi18n::getText('Display Balance on Pay Stub'),
									'minimum_time' => TTi18n::getText('Minimum Time'),
									'maximum_time' => TTi18n::getText('Maximum Time'),
									'apply_frequency_id' => TTi18n::getText('Frequency'),
									'apply_frequency_month' => TTi18n::getText('Frequency Month'),
									'apply_frequency_day_of_month' => TTi18n::getText('Frequency Day of Month'),
									'apply_frequency_day_of_week' => TTi18n::getText('Frequency Day of Week'),
									'apply_frequency_hire_date' => TTi18n::getText('Frequency Hire Date'),
									'milestone_rollover_month' => TTi18n::getText('Rollover Month'),
									'milestone_rollover_day_of_month' => TTi18n::getText('Rollover Day of Month'),
									'milestone_rollover_hire_date' => TTi18n::getText('Rollover Hire Date'),
									'minimum_employed_days' => TTi18n::getText('Minimum Employed Days'),
									);
					break;
				case 'AccrualPolicyMilestoneFactory':
				case 'AccrualPolicyMilestoneListFactory':
					$columns = array(
									'length_of_service_days' => TTi18n::getText('Length Of Service in Days'),
									);
					break;
				case 'PremiumPolicyFactory':
				case 'PremiumPolicyListFactory':
					$columns = array(
									'minimum_time' => TTi18n::getText('Minimum Time'),
									'maximum_time' => TTi18n::getText('Maximum Time'),
									'include_break_policy' => TTi18n::getText('Include Break Policy in Calculation'),
									'include_meal_policy' => TTi18n::getText('Include Meal Policy in Calculation'),
									'include_partial_punch' => TTi18n::getText('Include Partial Punches'),
									'daily_trigger_time' => TTi18n::getText('Active After Daily (Regular) Hours'),
									'weekly_trigger_time' => TTi18n::getText('Active After Weekly (Regular) Hours'),
									'start_time' => TTi18n::getText('Start Time'),
									'end_time' => TTi18n::getText('End Time'),
									'start_date' => TTi18n::getText('Start Date'),
									'end_date' => TTi18n::getText('End Date'),
									'sun' => TTi18n::getText('Effective Day: Sun'),
									'mon' => TTi18n::getText('Effective Day: Mon'),
									'tue' => TTi18n::getText('Effective Day: Tue'),
									'wed' => TTi18n::getText('Effective Day: Wed'),
									'thu' => TTi18n::getText('Effective Day: Thu'),
									'fri' => TTi18n::getText('Effective Day: Fri'),
									'sat' => TTi18n::getText('Effective Day: Sat'),
									'exclude_default_branch' => TTi18n::getText('Exclude Default Branch'),
									'exclude_default_department' => TTi18n::getText('Exclude Default Department'),
									'branch_selection_type_id' => TTi18n::getText('Branch Selection Type'),
									'department_selection_type_id' => TTi18n::getText('Department Selection Type'),
									'minimum_break_time' => TTi18n::getText('Minimum Time Recognized As Break'),
									'maximum_no_break_time' => TTi18n::getText('Maximum Time Without A Break'),
									);
					break;
				case 'HolidayPolicyFactory':
				case 'HolidayPolicyListFactory':
					$columns = array(
									'time' => TTi18n::getText('Holiday Time'),
									'worked_scheduled_days' => TTi18n::getText('Worked Before Type'),
									'minimum_worked_days' => TTi18n::getText('Work Before Days'),
									'minimum_worked_period_days' => TTi18n::getText('Work Before Limit Days'),

									'worked_after_scheduled_days' => TTi18n::getText('Worked After Type'),
									'minimum_worked_after_days' => TTi18n::getText('Work After Days'),
									'minimum_worked_after_period_days' => TTi18n::getText('Work After Limit Days'),

									'minimum_time' => TTi18n::getText('Minimum Time'),
									'maximum_time' => TTi18n::getText('Maximum Time'),
									'average_time_days' => TTi18n::getText('Days To Average Time Over'),
									'average_time_worked_days' => TTi18n::getText('Worked Days Only'),

									'force_over_time_policy' => TTi18n::getText('Always Apply Over Time Policy'),
									'include_over_time' => TTi18n::getText('Include Over Time in Average'),
									'include_paid_absence_time' => TTi18n::getText('Include Paid Absence Time in Average'),

									);
					break;
				case 'RecurringHolidayFactory':
				case 'RecurringHolidayListFactory':
					$columns = array(
									'month_int' => TTi18n::getText('Month'),
									'day_of_month' => TTi18n::getText('Day Of The Month'),
									'day_of_week' => TTi18n::getText('Day Of Week'),
									'week_interval' => TTi18n::getText('Week Interval'),
									'pivot_day_direction_id' => TTi18n::getText('Pivot Day Direction'),
									'special_day' => TTi18n::getText('Special Day'),
									'always_week_day_id' => TTi18n::getText('Always On Week Day'),
									);
					break;
				case 'PayPeriodScheduleFactory':
				case 'PayPeriodScheduleListFactory':
					$columns = array(
									'annual_pay_periods' => TTi18n::getText('Annual Pay Periods'),
									'start_day_of_week' => TTi18n::getText('Pay Period Starts On'),
									'start_week_day_id' => TTi18n::getText('Overtime Week'),
									'shift_assigned_day_id' => TTi18n::getText('Assign Shifts To'),
									'maximum_shift_time' => TTi18n::getText('Maximum Shift Time'),
									'new_day_trigger_time' => TTi18n::getText('Minimum Time-Off Between Shifts'),
									'timesheet_verify_type_id' => TTi18n::getText('TimeSheet Verification'),
									'timesheet_verify_before_end_date' => TTi18n::getText('Verification Window Starts'),
									'timesheet_verify_before_transaction_date' => TTi18n::getText('Verification Window Ends'),
									'anchor_date' => TTi18n::getText('Initial Date'),
									'transaction_date' => TTi18n::getText('Transaction Date'),
									'transaction_date_bd' => TTi18n::getText('Transaction Always On Business Day'),
									'primary_day_of_month' => TTi18n::getText('Primary Start Day'),
									'primary_transaction_day_of_month' => TTi18n::getText('Primary Transaction Day'),
									'secondary_day_of_month' => TTi18n::getText('Secondary Start Day'),
									'secondary_transaction_day_of_month' => TTi18n::getText('Secondary Transaction Day'),
									);
					break;
				case 'PayStubAmendmentFactory':
				case 'PayStubAmendmentListFactory':
				case 'RecurringPayStubAmendmentFactory':
				case 'RecurringPayStubAmendmentListFactory':
					$columns = array(
									'percent_amount' => TTi18n::getText('Percent'),
									'percent_amount_entry_name_id' => TTi18n::getText('Percent Of'),
									);
					break;
				case 'RecurringScheduleControlFactory':
				case 'RecurringScheduleControlListFactory':
					$columns = array(
									'start_week' => TTi18n::getText('Start Week'),
									);
					break;
				case 'RecurringScheduleTemplateControlFactory':
				case 'RecurringScheduleTemplateControlListFactory':
				case 'RecurringScheduleTemplateFactory':
				case 'RecurringScheduleTemplateListFactory':
					$columns = array(
									'recurring_schedule_template_control_id' => TTi18n::getText('Recurring Schedule Template'),
									'mon' => TTi18n::getText('Monday'),
									'tue' => TTi18n::getText('Tuesday'),
									'wed' => TTi18n::getText('Wednesday'),
									'thu' => TTi18n::getText('Thursday'),
									'fri' => TTi18n::getText('Friday'),
									'sat' => TTi18n::getText('Saturday'),
									'sun' => TTi18n::getText('Sunday'),
									);
					break;
				case 'UserPreferenceFactory':
				case 'UserPreferenceListFactory':
					$columns = array(
									'language' => TTi18n::gettext('Language'),
									'date_format' => TTi18n::gettext('Date Format'),
									'time_format' => TTi18n::gettext('Time Format'),
									'time_zone' => TTi18n::gettext('TimeZone'),
									'time_unit_format' => TTi18n::gettext('Time Unit Format'),
									'start_week_day' => TTi18n::gettext('Start Weekday'),
									'enable_email_notification_exception' => TTi18n::gettext('Email Notification Exception'),
									'enable_email_notification_message' => TTi18n::gettext('Email Notification Message'),
									'enable_email_notification_home' => TTi18n::gettext('Email Notification Home'),
									);
					break;
				case 'CompanyDeductionFactory':
				case 'CompanyDeductionListFactory':
				case 'UserDeductionFactory':
				case 'UserDeductionListFactory':
					$columns = array(
									'minimum_length_of_service_unit_id' => TTi18n::gettext('Minimum Length Of Service Units'),
									'minimum_length_of_service_days' => TTi18n::gettext('Minimum Length Of Service Days'),
									'minimum_length_of_service' => TTi18n::gettext('Minimum Length Of Service'),
									'maximum_length_of_service_unit_id' => TTi18n::gettext('Maximum Length Of Service Units'),
									'maximum_length_of_service_days' => TTi18n::gettext('Maximum Length Of Service Days'),
									'maximum_length_of_service' => TTi18n::gettext('Maximum Length Of Service'),
									'calculation_order' => TTi18n::gettext('Calculation Order'),
									'include_account_amount_type_id' => TTi18n::gettext('Include PS Account Value'),
									'exclude_account_amount_type_id' => TTi18n::gettext('Exclude PS Account Value'),
									'user_value1' => TTi18n::gettext('Value 1'),
									'user_value2' => TTi18n::gettext('Value 2'),
									'user_value3' => TTi18n::gettext('Value 3'),
									'user_value4' => TTi18n::gettext('Value 4'),
									'user_value5' => TTi18n::gettext('Value 5'),
									'user_value5' => TTi18n::gettext('Value 6'),
									'user_value5' => TTi18n::gettext('Value 7'),
									'user_value5' => TTi18n::gettext('Value 8'),
									'user_value5' => TTi18n::gettext('Value 9'),
									'user_value5' => TTi18n::gettext('Value 10'),
									);
					break;
				case 'JobFactory':
				case 'JobListFactory':
					$columns = array(
									'supervisor_user_id' => TTi18n::gettext('Supervisor'),
									'default_item_id' => TTi18n::gettext('Default Task'),
									'user_branch_selection_type_id' => TTi18n::gettext('Branch Selection Type'),
									'user_department_selection_type_id' => TTi18n::gettext('Department Selection Type'),
									'user_group_selection_type_id' => TTi18n::gettext('Group Selection Type'),
									'job_item_group_selection_type_id' => TTi18n::gettext('Task Group Selection Type'),
									);
					break;
				case 'DocumentFactory':
				case 'DocumentListFactory':
				case 'DocumentRevisionFactory':
				case 'DocumentRevisionListFactory':
					$columns = array(
									'document_id' => TTi18n::gettext('Document'),
									'mime_type' => TTi18n::gettext('MIME Type'),
									'local_file_name' => TTi18n::gettext('Local File Name'),
									);
					break;
				case 'ClientPaymentFactory':
				case 'ClientPaymentFactory':
					$columns = array(
									'cc_bank_phone' => TTi18n::gettext('Issuing Bank Phone Number'),
									'cc_expire' => TTi18n::gettext('Expiry Date'),
									'cc_name' => TTi18n::gettext('Card Holder Name'),
									'cc_number' => TTi18n::gettext('Credit Card Number'),
									'cc_check' => TTi18n::gettext('Security Code'),
									'bank_account' => TTi18n::gettext('Bank Account'),
									'bank_transit' => TTi18n::gettext('Bank Routing/Transit'),
									'bank_institution' => TTi18n::gettext('Bank Institution'),
									);
					break;
				case 'ProductFactory':
				case 'ProductFactory':
					$columns = array(
									'group_id' => TTi18n::gettext('Group'),
									'minimum_purchase_quantity' => TTi18n::gettext('Minimum Purchase Quantity'),
									'maximum_purchase_quantity' => TTi18n::gettext('Maximum Purchase Quantity'),
									'price_locked' => TTi18n::gettext('Lock Price'),
									'description_locked' => TTi18n::gettext('Lock Description '),
									'dimension_unit_id' => TTi18n::gettext('Dimension Unit'),
									'weight_unit_id' => TTi18n::gettext('Weight Unit'),
									'origin_country' => TTi18n::gettext('Origin Country'),
									'tariff_code' => TTi18n::gettext('Tariff Code'),
									'customs_unit_value' => TTi18n::gettext('Customs Unit Value'),
									'unit_cost' => TTi18n::gettext('Unit Cost'),
									'unit_price_type_id' => TTi18n::gettext('Price Type'),
									'upc' => TTi18n::gettext('UPC'),
									);
			}

			$display_field = Option::getByKey($field, array_merge($global_columns, $columns) );

			//Try getting the column name from the class 'column' array.
			if ( $display_field == '' ) {
				$columns = Misc::trimSortPrefix( $obj->getOptions('columns') );
				$display_field = Option::getByKey($field, $columns );

				if ( $display_field == '' AND strpos( $field, '_id') ) {
					$tmp_field = $this->normalizeField( $obj, $field );
					$display_field = Option::getByKey($tmp_field, $columns );
				}
			}
			Debug::text('Converting: '. $field .' to Display for Class: '. $class .' Field: '. $field .' Retval: '. $display_field, __FILE__, __LINE__, __METHOD__, 10);
		}

		if ( $display_field == '' ) {
			$display_field = ucfirst($field);
		}

		return $display_field;
	}

	//Converts raw data from the database into something useful to the user.
	//ie: branch_id = 3 to "Westbank"
	function getDisplayValue( $obj, $value ) {
		$retval = $value;

		$class = get_class($obj);
		$field = $this->getField();

		switch ( $field ) {
			//Global variables, like branches,departments,titles,jobs,tasks,status_id,type_id etc...
			case 'branch_id':
			case 'default_branch_id':
				$lf = new BranchListFactory();
				$lf->getById( $value );
				if ( $lf->getRecordCount() > 0 ) {
					$retval = $lf->getCurrent()->getName();
				} else {
					$retval = TTi18n::getText('--');
				}
				break;
			case 'department_id':
			case 'default_department_id':
				$lf = new DepartmentListFactory();
				$lf->getById( $value );
				if ( $lf->getRecordCount() > 0 ) {
					$retval = $lf->getCurrent()->getName();
				} else {
					$retval = TTi18n::getText('--');
				}
				break;
			case 'title_id':
				$lf = new UserTitleListFactory();
				$lf->getById( $value );
				if ( $lf->getRecordCount() > 0 ) {
					$retval = $lf->getCurrent()->getName();
				} else {
					$retval = TTi18n::getText('--');
				}
				break;
			case 'currency_id':
				$lf = new CurrencyListFactory();
				$lf->getById( $value );
				if ( $lf->getRecordCount() > 0 ) {
					$retval = $lf->getCurrent()->getName();
				} else {
					$retval = TTi18n::getText('--');
				}
				break;
			case 'group_id':
				$lf = new UserGroupListFactory();
				$lf->getById( $value );
				if ( $lf->getRecordCount() > 0 ) {
					$retval = $lf->getCurrent()->getName();
				} else {
					$retval = TTi18n::getText('--');
				}
				break;
			case 'supervisor_user_id':
			case 'support_contact':
			case 'sales_contact':
			case 'user_id':
				$lf = new UserListFactory();
				$lf->getById( $value );
				if ( $lf->getRecordCount() > 0 ) {
					$retval = $lf->getCurrent()->getFullName();
				} else {
					$retval = TTi18n::getText('--');
				}
				break;
			case 'accrual_policy_id':
				$lf = new AccrualPolicyListFactory();
				$lf->getById( $value );
				if ( $lf->getRecordCount() > 0 ) {
					$retval = $lf->getCurrent()->getName();
				} else {
					$retval = TTi18n::getText('--');
				}
				break;
			case 'schedule_policy_id':
				$lf = new SchedulePolicyListFactory();
				$lf->getById( $value );
				if ( $lf->getRecordCount() > 0 ) {
					$retval = $lf->getCurrent()->getName();
				} else {
					$retval = TTi18n::getText('--');
				}
				break;
			case 'exception_policy_control_id':
				$lf = new ExceptionPolicyListFactory();
				$lf->getById( $value );
				if ( $lf->getRecordCount() > 0 ) {
					$retval = $lf->getCurrent()->getName();
				} else {
					$retval = TTi18n::getText('--');
				}
				break;
			case 'meal_policy_id':
				$lf = new MealPolicyListFactory();
				$lf->getById( $value );
				if ( $lf->getRecordCount() > 0 ) {
					$retval = $lf->getCurrent()->getName();
				} else {
					$retval = TTi18n::getText('--');
				}
				break;
			case 'break_policy_id':
				$lf = new BreakPolicyListFactory();
				$lf->getById( $value );
				if ( $lf->getRecordCount() > 0 ) {
					$retval = $lf->getCurrent()->getName();
				} else {
					$retval = TTi18n::getText('--');
				}
				break;
			case 'absence_policy_id':
				$lf = new AbsencePolicyListFactory();
				$lf->getById( $value );
				if ( $lf->getRecordCount() > 0 ) {
					$retval = $lf->getCurrent()->getName();
				} else {
					$retval = TTi18n::getText('--');
				}
				break;
			case 'over_time_policy_id':
				$lf = new OverTimePolicyListFactory();
				$lf->getById( $value );
				if ( $lf->getRecordCount() > 0 ) {
					$retval = $lf->getCurrent()->getName();
				} else {
					$retval = TTi18n::getText('--');
				}
				break;
			case 'holiday_policy_id':
				$lf = new HolidayPolicyListFactory();
				$lf->getById( $value );
				if ( $lf->getRecordCount() > 0 ) {
					$retval = $lf->getCurrent()->getName();
				} else {
					$retval = TTi18n::getText('--');
				}
				break;
			case 'round_interval_policy_id':
				$lf = new RoundIntervalPolicyListFactory();
				$lf->getById( $value );
				if ( $lf->getRecordCount() > 0 ) {
					$retval = $lf->getCurrent()->getName();
				} else {
					$retval = TTi18n::getText('--');
				}
				break;
			case 'wage_group_id':
				$lf = new WageGroupListFactory();
				$lf->getById( $value );
				if ( $lf->getRecordCount() > 0 ) {
					$retval = $lf->getCurrent()->getName();
				} else {
					$retval = TTi18n::getText('--');
				}
				break;
			case 'company_deduction_id':
				$lf = new CompanyDeductionListFactory();
				$lf->getById( $value );
				if ( $lf->getRecordCount() > 0 ) {
					$retval = $lf->getCurrent()->getName();
				} else {
					$retval = TTi18n::getText('--');
				}
				break;
			case 'pay_stub_entry_account_id':
			case 'pay_stub_entry_name_id':
			case 'accrual_pay_stub_entry_account_id':
			case 'percent_amount_entry_name_id':
				$lf = new PayStubEntryAccountListFactory();
				$lf->getById( $value );
				if ( $lf->getRecordCount() > 0 ) {
					$type_options  = $lf->getOptions('type');
					$retval = $type_options[$lf->getCurrent()->getType()] .' - '. $lf->getCurrent()->getName();
				} else {
					$retval = TTi18n::getText('--');
				}
				break;
			case 'recurring_schedule_template_control_id':
				$lf = new RecurringScheduleTemplateControlListFactory();
				$lf->getById( $value );
				if ( $lf->getRecordCount() > 0 ) {
					$retval = $lf->getCurrent()->getName();
				} else {
					$retval = TTi18n::getText('--');
				}
				break;

			//
			//Start Professional Edition tables
			//
			case 'job_id':
				if ( getTTProductEdition() == TT_PRODUCT_PROFESSIONAL ) {
					$lf = new JobListFactory();
					$lf->getById( $value );
					if ( $lf->getRecordCount() > 0 ) {
						$retval = $lf->getCurrent()->getName();
					} else {
						$retval = TTi18n::getText('--');
					}
				}
				break;
			case 'default_item_id':
			case 'job_item_id':
				if ( getTTProductEdition() == TT_PRODUCT_PROFESSIONAL ) {
					$lf = new JobItemListFactory();
					$lf->getById( $value );
					if ( $lf->getRecordCount() > 0 ) {
						$retval = $lf->getCurrent()->getName();
					} else {
						$retval = TTi18n::getText('--');
					}
				}
				break;
			case 'client_id':
				if ( getTTProductEdition() == TT_PRODUCT_PROFESSIONAL ) {
					$lf = new ClientListFactory();
					$lf->getById( $value );
					if ( $lf->getRecordCount() > 0 ) {
						$retval = $lf->getCurrent()->getCompanyName();
					} else {
						$retval = TTi18n::getText('--');
					}
				}
				break;
			case 'product_id':
				if ( getTTProductEdition() == TT_PRODUCT_PROFESSIONAL ) {
					$lf = new ProductListFactory();
					$lf->getById( $value );
					if ( $lf->getRecordCount() > 0 ) {
						$retval = $lf->getCurrent()->getName();
					} else {
						$retval = TTi18n::getText('--');
					}
				}
				break;
			case 'document_id':
				if ( getTTProductEdition() == TT_PRODUCT_PROFESSIONAL ) {
					$lf = new DocumentListFactory();
					$lf->getById( $value );
					if ( $lf->getRecordCount() > 0 ) {
						$retval = $lf->getCurrent()->getName();
					} else {
						$retval = TTi18n::getText('--');
					}
				}
				break;
			case 'invoice_district_id':
				if ( getTTProductEdition() == TT_PRODUCT_PROFESSIONAL ) {
					$lf = new InvoiceDistrictListFactory();
					$lf->getById( $value );
					if ( $lf->getRecordCount() > 0 ) {
						$retval = $lf->getCurrent()->getName();
					} else {
						$retval = TTi18n::getText('--');
					}
				}
				break;
			//
			//End Professional Edition tables
			//

			case 'status_id':
			case 'type_id':
				$options = $obj->getOptions( $this->normalizeField( $obj, $field) );
				$retval = Option::getByKey($value, $options );
				break;
			case 'origin_country':
			case 'country':
				$cf = new CompanyFactory();
				$retval = Option::getByKey($value, $cf->getOptions( 'country') );
				break;
			case 'province': //Cant do this one, as it requires the country value too.
				break;
			case 'time_zone':
				$upf = new UserPreferenceFactory();
				$retval = Option::getByKey($value, Misc::TrimSortPrefix( $upf->getOptions( $field ) ) );
				break;
			case 'time_stamp':
			case 'start_time':
			case 'end_time':
				$retval = TTDate::getDate('DATE+TIME', $value );
				break;
			case 'date_stamp':
			case 'start_date':
			case 'end_date':
			case 'transaction_date':
				if ( $value != '' ) {
					$retval = TTDate::getDate('DATE', $value );
				} else {
					$retval = NULL;
				}
				break;
			case 'total_time':
				$retval = TTDate::getTimeUnit( $value );
				break;
			case 'authorized':
				$retval = Misc::HumanBoolean( $value );
				break;

		}

		//Handle class specific fields.
		switch ( $class ) {
			case 'UserFactory':
			case 'UserListFactory':
				switch ( $field ) {
					case 'sin':
						$retval = $obj->getSecureSIN( $value );
						break;
					case 'hire_date':
					case 'birth_date':
					case 'termination_date':
						$retval = TTDate::getDate('DATE', $value );
						break;
				}
				break;
			case 'CurrencyFactory':
			case 'CurrencyListFactory':
				switch ( $field ) {
					case 'actual_rate_updated_date':
						$retval = TTDate::getDate('DATE', $value );
						break;
					case 'is_default':
					case 'is_base':
					case 'auto_update':
						$retval = Misc::HumanBoolean( $value );
						break;
				}
				break;
			case 'AccrualFactory':
			case 'AccrualListFactory':
				switch ( $field ) {
					case 'amount':
						$retval = TTDate::getTimeUnit( $value );
						break;
				}
				break;
			case 'StationFactory':
			case 'StationListFactory':
				switch ( $field ) {
					case 'branch_selection_type_id':
						$retval = Option::getByKey($value, $obj->getOptions('branch_selection_type') );
						break;
					case 'department_selection_type_id':
						$retval = Option::getByKey($value, $obj->getOptions('department_selection_type') );
						break;
					case 'group_selection_type_id':
						$retval = Option::getByKey($value, $obj->getOptions('group_selection_type') );
						break;
					case 'partial_push_frequency':
						$retval = Option::getByKey($value, $obj->getOptions('partial_push_frequency') );
						break;
					case 'push_frequency':
						$retval = Option::getByKey($value, $obj->getOptions('push_frequency') );
						break;
					case 'poll_frequency':
						$retval = Option::getByKey($value, $obj->getOptions('poll_frequency') );
						break;
					case 'enable_auto_punch_status':
						$retval = Misc::HumanBoolean( $value );
						break;
				}
				break;
			case 'SchedulePolicyFactory':
			case 'SchedulePolicyListFactory':
				switch ( $field ) {
					case 'start_stop_window':
						$retval = TTDate::getTimeUnit( $value );
						break;
				}
				break;
			case 'RoundIntervalPolicyFactory':
			case 'RoundIntervalPolicyListFactory':
				switch ( $field ) {
					case 'punch_type_id':
					case 'round_type_id':
						$options = $obj->getOptions( $this->normalizeField( $obj, $field) );
						$retval = Option::getByKey($value, $options );
						break;
					case 'grace':
					case 'round_interval':
						$retval = TTDate::getTimeUnit( $value );
						break;
					case 'strict':
						$retval = Misc::HumanBoolean( $value );
						break;
				}
				break;
			case 'MealPolicyFactory':
			case 'MealPolicyListFactory':
			case 'BreakPolicyFactory':
			case 'BreakPolicyListFactory':
				switch ( $field ) {
					case 'auto_detect_type_id':
						$options = $obj->getOptions( $this->normalizeField( $obj, $field) );
						$retval = Option::getByKey($value, $options );
						break;
					case 'start_window':
					case 'window_length':
					case 'minimum_punch_time':
					case 'maximum_punch_time':
					case 'trigger_time':
					case 'amount':
						$retval = TTDate::getTimeUnit( $value );
						break;
					case 'include_lunch_punch_time':
					case 'include_break_punch_time':
						$retval = Misc::HumanBoolean( $value );
						break;
				}
				break;
			case 'AccrualPolicyFactory':
			case 'AccrualPolicyListFactory':
				switch ( $field ) {
					case 'apply_frequency_id':
						$options = $obj->getOptions( $this->normalizeField( $obj, $field) );
						$retval = Option::getByKey($value, $options );
						break;
					case 'apply_frequency_month':
					case 'milestone_rollover_month':
						$options = TTDate::getMonthOfYearArray();
						$retval = Option::getByKey($value, $options );
						break;
					case 'apply_frequency_day_of_week':
						$options = TTDate::getDayOfWeekArray();
						$retval = Option::getByKey($value, $options );
						break;
					case 'enable_pay_stub_balance_display':
					case 'milestone_rollover_hire_date':
						$retval = Misc::HumanBoolean( $value );
						break;
				}
				break;
			case 'AccrualPolicyMilestoneFactory':
			case 'AccrualPolicyMilestoneListFactory':
				switch ( $field ) {
					case 'length_of_service_unit_id':
						$options = $obj->getOptions( $this->normalizeField( $obj, $field) );
						$retval = Option::getByKey($value, $options );
						break;
					case 'maximum_time':
					case 'accrual_rate':
						$retval = TTDate::getTimeUnit( $value );
						break;
				}
				break;
			case 'OverTimePolicyFactory':
			case 'OverTimePolicyListFactory':
				switch ( $field ) {
					case 'trigger_time':
						$retval = TTDate::getTimeUnit( $value );
						break;
				}
				break;
			case 'PremiumPolicyFactory':
			case 'PremiumPolicyListFactory':
				switch ( $field ) {
					case 'pay_type_id':
					case 'branch_selection_type_id':
					case 'department_selection_type_id':
						$options = $obj->getOptions( $this->normalizeField( $obj, $field) );
						$retval = Option::getByKey($value, $options );
						break;
					case 'daily_trigger_time':
					case 'weekly_trigger_time':
					case 'minimum_time':
					case 'maximum_time':
					case 'minimum_break_time':
					case 'maximum_no_break_time':
						$retval = TTDate::getTimeUnit( $value );
						break;
					case 'start_time':
					case 'end_time':
						$retval = TTDate::getDate('TIME', $value );
						break;
					case 'sun':
					case 'mon':
					case 'tue':
					case 'wed':
					case 'thu':
					case 'fri':
					case 'sat':
					case 'include_break_policy':
					case 'include_meal_policy':
					case 'include_partial_punch':
					case 'exclude_default_branch':
					case 'exclude_default_department':
						$retval = Misc::HumanBoolean( $value );
						break;
				}
				break;
			case 'HolidayPolicyFactory':
			case 'HolidayPolicyListFactory':
				switch ( $field ) {
					case 'worked_scheduled_days':
					case 'worked_after_scheduled_days':
						$options = $obj->getOptions( 'scheduled_day' );
						$retval = Option::getByKey($value, $options );
						break;
					case 'default_schedule_status_id':
						$options = $obj->getOptions( $this->normalizeField( $obj, $field) );
						$retval = Option::getByKey($value, $options );
						break;
					case 'time':
					case 'minimum_time':
					case 'maximum_time':
						$retval = TTDate::getTimeUnit( $value );
						break;
					case 'include_paid_absence_time':
					case 'include_over_time':
					case 'force_over_time_policy':
					case 'average_time_worked_days':
						$retval = Misc::HumanBoolean( $value );
						break;
				}
				break;
			case 'RecurringHolidayFactory':
			case 'RecurringHolidayListFactory':
				switch ( $field ) {
					case 'special_day':
					case 'week_interval':
					case 'always_week_day_id':
					case 'pivot_day_direction_id':
						$options = $obj->getOptions( $this->normalizeField( $obj, $field) );
						$retval = Option::getByKey($value, $options );
						break;
					case 'day_of_week':
						$retval = Option::getByKey($value, TTDate::getDayOfWeekArray() );
						break;
					case 'month_int':
						$retval = Option::getByKey($value, TTDate::getMonthOfYearArray() );
						break;
				}
				break;

			case 'ExceptionPolicyFactory':
			case 'ExceptionPolicyListFactory':
				switch ( $field ) {
					case 'email_notification_id':
					case 'severity_id':
						$options = $obj->getOptions( $this->normalizeField( $obj, $field) );
						$retval = Option::getByKey($value, $options );
						break;
					case 'grace':
					case 'watch_window':
						$retval = TTDate::getTimeUnit( $value );
						break;
					case 'active':
						$retval = Misc::HumanBoolean( $value );
						break;
				}
				break;
			case 'PayPeriodScheduleFactory':
			case 'PayPeriodScheduleListFactory':
				switch ( $field ) {
					case 'shift_assigned_day_id':
					case 'start_week_day_id':
					case 'timesheet_verify_type_id':
						$options = $obj->getOptions( $this->normalizeField( $obj, $field) );
						$retval = Option::getByKey($value, $options );
						break;
					case 'transaction_date_bd':
						$options = $obj->getOptions( 'transaction_date_business_day' );
						$retval = Option::getByKey($value, $options );
						break;
					case 'start_day_of_week':
						$retval = Option::getByKey($value, TTDate::getDayOfWeekArray()  );
						break;
					case 'maximum_shift_time':
					case 'new_day_trigger_time':
						$retval = TTDate::getTimeUnit( $value );
						break;
					case 'anchor_date':
						$retval = TTDate::getDate('DATE', $value );
						break;
					case 'active':
						$retval = Misc::HumanBoolean( $value );
						break;
					case 'transaction_date': //Not an actual date, but how long after PP ends.
						$retval = $value;
						break;
				}
				break;
			case 'PayStubAmendmentFactory':
			case 'PayStubAmendmentListFactory':
			case 'RecurringPayStubAmendmentFactory':
			case 'RecurringPayStubAmendmentListFactory':
				switch ( $field ) {
					case 'effective_date':
						$retval = TTDate::getDate('DATE', $value );
						break;
					case 'ytd_adjustment':
						$retval = Misc::HumanBoolean( $value );
						break;
				}
				break;
			case 'RecurringPayStubAmendmentFactory':
			case 'RecurringPayStubAmendmentListFactory':
				switch ( $field ) {
					case 'frequency_id':
						$options = $obj->getOptions( $this->normalizeField( $obj, $field) );
						$retval = Option::getByKey($value, $options );
						break;
				}
				break;
			case 'RecurringScheduleControlFactory':
			case 'RecurringScheduleControlListFactory':
				switch ( $field ) {
					case 'auto_fill':
						$retval = Misc::HumanBoolean( $value );
						break;
				}
				break;
			case 'RecurringScheduleTemplateControlFactory':
			case 'RecurringScheduleTemplateControlListFactory':
			case 'RecurringScheduleTemplateFactory':
			case 'RecurringScheduleTemplateListFactory':
				switch ( $field ) {
					case 'sun':
					case 'mon':
					case 'tue':
					case 'wed':
					case 'thu':
					case 'fri':
					case 'sat':
					case 'include_break_policy':
					case 'include_meal_policy':
					case 'include_partial_punch':
					case 'exclude_default_branch':
					case 'exclude_default_department':
						$retval = Misc::HumanBoolean( $value );
						break;
					case 'start_time':
					case 'end_time':
						$retval = TTDate::getDate('TIME', $value );
						break;
				}
				break;
			case 'UserPreferenceFactory':
			case 'UserPreferenceListFactory':
				switch ( $field ) {
					case 'start_week_day':
					case 'time_unit_format':
					case 'time_format':
					case 'date_format':
						$options = Misc::trimSortPrefix( $obj->getOptions( $this->normalizeField( $obj, $field) ) );
						$retval = Option::getByKey($value, $options );
						break;
					case 'enable_email_notification_home':
					case 'enable_email_notification_message':
					case 'enable_email_notification_exception':
						$retval = Misc::HumanBoolean( $value );
						break;
				}
				break;
			case 'UserWageFactory':
			case 'UserWageListFactory':
				switch ( $field ) {
					case 'effective_date':
						$retval = TTDate::getDate('DATE', $value );
						break;
					case 'weekly_time':
						$retval = TTDate::getTimeUnit( $value );
						break;
				}
				break;
			case 'CompanyDeductionFactory':
			case 'CompanyDeductionListFactory':
				switch ( $field ) {
					case 'calculation_id':
						$options = Misc::trimSortPrefix( $obj->getOptions( $this->normalizeField( $obj, $field) ) );
						$retval = Option::getByKey($value, $options );
						break;
					case 'include_account_amount_type_id':
					case 'exclude_account_amount_type_id':
						$options = Misc::trimSortPrefix( $obj->getOptions( 'account_amount_type' ) );
						$retval = Option::getByKey($value, $options );
						break;
					case 'minimum_length_of_service_unit_id':
					case 'maximum_length_of_service_unit_id':
						$options = Misc::trimSortPrefix( $obj->getOptions( 'length_of_service_unit' ) );
						$retval = Option::getByKey($value, $options );
						break;
				}
				break;
			case 'JobFactory':
			case 'JobListFactory':
				switch ( $field ) {
					case 'group_id':
						$lf = new JobGroupListFactory();
						$lf->getById( $value );
						if ( $lf->getRecordCount() > 0 ) {
							$retval = $lf->getCurrent()->getName();
						} else {
							$retval = TTi18n::getText('--');
						}
						break;
					case 'user_branch_selection_type_id':
						$retval = Option::getByKey($value, $obj->getOptions('user_branch_selection_type') );
						break;
					case 'user_department_selection_type_id':
						$retval = Option::getByKey($value, $obj->getOptions('user_department_selection_type') );
						break;
					case 'user_group_selection_type_id':
						$retval = Option::getByKey($value, $obj->getOptions('user_group_selection_type') );
						break;
					case 'job_item_group_selection_type_id':
						$retval = Option::getByKey($value, $obj->getOptions('job_item_group_selection_type') );
						break;
					case 'estimate_time':
					case 'minimum_time':
						$retval = TTDate::getTimeUnit( $value );
						break;

				}
				break;
			case 'JobItemFactory':
			case 'JobItemListFactory':
				switch ( $field ) {
					case 'group_id':
						$lf = new JobItemGroupListFactory();
						$lf->getById( $value );
						if ( $lf->getRecordCount() > 0 ) {
							$retval = $lf->getCurrent()->getName();
						} else {
							$retval = TTi18n::getText('--');
						}
						break;
					case 'estimate_time':
					case 'minimum_time':
						$retval = TTDate::getTimeUnit( $value );
						break;
				}
				break;
			case 'DocumentFactory':
			case 'DocumentListFactory':
				switch ( $field ) {
					case 'group_id':
						$lf = new DocumentGroupListFactory();
						$lf->getById( $value );
						if ( $lf->getRecordCount() > 0 ) {
							$retval = $lf->getCurrent()->getName();
						} else {
							$retval = TTi18n::getText('--');
						}
						break;
					case 'template':
					case 'private':
						$retval = Misc::HumanBoolean( $value );
						break;
				}
				break;
			case 'ClientFactory':
			case 'ClientListFactory':
				switch ( $field ) {
					case 'group_id':
						$lf = new ClientGroupListFactory();
						$lf->getById( $value );
						if ( $lf->getRecordCount() > 0 ) {
							$retval = $lf->getCurrent()->getName();
						} else {
							$retval = TTi18n::getText('--');
						}
						break;
				}
				break;
			case 'ClientContactFactory':
			case 'ClientContactListFactory':
				switch ( $field ) {
					case 'is_default':
						$retval = Misc::HumanBoolean( $value );
						break;
				}
				break;
			case 'ProductFactory':
			case 'ProductListFactory':
				switch ( $field ) {
					case 'group_id':
						$lf = new ProductGroupListFactory();
						$lf->getById( $value );
						if ( $lf->getRecordCount() > 0 ) {
							$retval = $lf->getCurrent()->getName();
						} else {
							$retval = TTi18n::getText('--');
						}
						break;

					case 'unit_price_type_id':
					case 'weight_unit_id':
					case 'dimension_unit_id':
						$options = $obj->getOptions( $this->normalizeField( $obj, $field) );
						$retval = Option::getByKey($value, $options );
						break;

					case 'price_locked':
					case 'description_locked':
						$retval = Misc::HumanBoolean( $value );
						break;
				}
				break;
		}

		Debug::text('  Converting: '. $value .' to Display Value for Class: '. $class .' Field: '. $field .' Retval: '. $retval, __FILE__, __LINE__, __METHOD__, 10);
		return $retval;
	}

	function getDisplayOldValue( $obj ) {
		return $this->getDisplayValue( $obj, $this->getOldValue() );
	}
	function getDisplayNewValue( $obj ) {
		return $this->getDisplayValue( $obj, $this->getNewValue() );
	}

	//When comparing the two arrays, if there are sub-arrays, we need to *always* include those, as we can't actually
	//diff the two, because they are already saved by the time we get to this function, so there will never be any changes to them.
	//We don't want to include sub-arrays, as the sub-classes should handle the logging themselves.
	function diffData( $arr1, $arr2 ) {
		if ( !is_array($arr1) OR !is_array($arr2) ) {
			return FALSE;
		}

		$retarr = FALSE;
		foreach( $arr1 as $key => $val ) {
			if ( !isset($arr2[$key]) OR is_array($val) OR is_array($arr2[$key]) OR ( $arr2[$key] != $val ) ) {
				$retarr[$key] = $val;
			}
		}

		return $retarr;
	}

	function addLogDetail( $action_id, $system_log_id, $object ) {
		$start_time = microtime(TRUE);

		//Only log detail records on add,edit,delete,undelete
		//Logging data on Add/Delete/UnDelete, or anything but Edit will greatly bloat the database, on the order of tens of thousands of entries
		//per day. The issue though is its nice to know exactly what data was originally added, then what was edited, and what was finally deleted.
		//We may need to remove logging for added data, but leave it for edit/delete, so we know exactly what data was deleted.
		if ( !in_array($action_id, array(10,20,30,31,40) ) ) {
			Debug::text('Invalid Action ID: '. $action_id, __FILE__, __LINE__, __METHOD__, 10);
			return FALSE;
		}

		if ( $system_log_id > 0 AND is_object($object) ) {
			$class = get_class( $object );
			Debug::text('System Log ID: '. $system_log_id .' Class: '. $class, __FILE__, __LINE__, __METHOD__, 10);
			//Debug::Arr($object->data, 'Object Data: ', __FILE__, __LINE__, __METHOD__, 10);
			//Debug::Arr($object->old_data, 'Object Old Data: ', __FILE__, __LINE__, __METHOD__, 10);

			//Only store raw data changes, don't convert *_ID fields to full text names, it bloats the storage and slows down the logging process too much.
			//We can do the conversion when someone actually looks at the audit logs, which will obviously be quite rare in comparison. Even though this will
			//require quite a bit more code to handle.
			//There are also translation issues if we convert IDs to text at this point. However there could be continuity problems if ID values change in the future.
			$new_data = $object->data;
			//Debug::Arr($new_data, 'New Data Arr: ', __FILE__, __LINE__, __METHOD__, 10);

			if ( $action_id == 20 ) { //Edit
				if ( method_exists( $object, 'setObjectFromArray' ) ) {
					//Run the old data back through the objects own setObjectFromArray(), so any necessary values can be parsed.
					$tmp_class = new $class;
					$tmp_class->setObjectFromArray( $object->old_data );
					$old_data = $tmp_class->data;
					unset($tmp_class);
				} else {
					$old_data = $object->old_data;
				}

				//We don't want to include any sub-arrays, as those classes should take care of their own logging, even though it may be slower in some cases.
				$diff_arr = array_diff_assoc( (array)$new_data, (array)$old_data );
			} elseif ( $action_id == 30 ) { //Delete
				$old_data = array();
				if ( method_exists( $object, 'setObjectFromArray' ) ) {
					//Run the old data back through the objects own setObjectFromArray(), so any necessary values can be parsed.
					$tmp_class = new $class;
					$tmp_class->setObjectFromArray( $object->data );
					$diff_arr = $tmp_class->data;
					unset($tmp_class);
				} else {
					$diff_arr = $object->data;
				}
			} else { //Add
				//Debug::text('Not editing, skipping the diff process...', __FILE__, __LINE__, __METHOD__, 10);
				$old_data = array();
				$diff_arr = $object->data;
			}
			//Debug::Arr($old_data, 'Old Data Arr: ', __FILE__, __LINE__, __METHOD__, 10);

			//Handle class specific fields.
			switch ( $class ) {
				case 'UserFactory':
				case 'UserListFactory':
					unset(
							$diff_arr['password'],
							$diff_arr['phone_password'],
							$diff_arr['full_name']
							);
					break;
				case 'StationFactory':
				case 'StationListFactory':
					unset(
							$diff_arr['last_poll_date'],
							$diff_arr['last_push_date'],
							$diff_arr['last_punch_time_stamp'],
							$diff_arr['last_partial_push_date'],
							$diff_arr['mode_flag'], //This is changed often for some reason, would be nice to audit it though.
							$diff_arr['work_code_definition'],
							$diff_arr['allowed_date']
						);
					break;
				case 'PunchFactory':
				case 'PunchListFactory':
					unset(
							$diff_arr['actual_time_stamp'],
							$diff_arr['original_time_stamp'],
							$diff_arr['punch_control_id'],
							$diff_arr['station_id']
							);
					break;
				case 'PunchControlFactory':
				case 'PunchControlListFactory':
					unset(
							//$diff_arr['user_date_id'],
							$diff_arr['actual_total_time']
							);
					break;
				case 'UserDateTotalFactory':
				case 'UserDateTotalListFactory':
					break;
				case 'AccrualFactory':
				case 'AccrualListFactory':
					unset(
							$diff_arr['user_date_total_id']
							);
					break;
				case 'ClientContactFactory':
				case 'ClientContactListFactory':
					unset(
							$diff_arr['password']
							);
					break;
				case 'ClientPaymentFactory':
				case 'ClientPaymentListFactory':
					if ( getTTProductEdition() == TT_PRODUCT_PROFESSIONAL ) {
						//Only log secure values.
						if ( isset($diff_arr['cc_number']) ) {
							$old_data['cc_number'] = $object->getSecureCreditCardNumber( Misc::decrypt( $old_data['cc_number'] ) );
							$new_data['cc_number'] = $object->getSecureCreditCardNumber( Misc::decrypt( $new_data['cc_number'] ) );
						}

						if ( isset($diff_arr['bank_account']) ) {
							$old_data['bank_account'] = $object->getSecureAccount( $old_data['bank_account'] );
							$new_data['bank_account'] = $object->getSecureAccount( $new_data['bank_account'] );
						}

						if ( isset($diff_arr['cc_check']) ) {
							$old_data['cc_check'] = $object->getSecureCreditCardCheck( $old_data['cc_check'] );
							$new_data['cc_check'] = $object->getSecureCreditCardCheck( $new_data['cc_check'] );
						}
					}
					break;
			}

			//Ignore specific columns here, like updated_date, updated_by, etc...
			unset(
					//These fields should never change, and therefore don't need to be recorded.
					$diff_arr['id'],
					$diff_arr['company_id'],

					$diff_arr['user_date_id'], //UserDateTotal, Schedule, PunchControl, etc...

					//General fields to skip
					$diff_arr['created_date'],
					$diff_arr['created_by'],
					$diff_arr['created_by_id'],
					$diff_arr['updated_date'],
					$diff_arr['updated_by'],
					$diff_arr['updated_by_id'],
					$diff_arr['deleted_date'],
					$diff_arr['deleted_by'],
					$diff_arr['deleted_by_id'],
					$diff_arr['deleted']
					);
			//Debug::Arr($diff_arr, 'Array Diff: ', __FILE__, __LINE__, __METHOD__, 10);

			if ( is_array($diff_arr) AND count($diff_arr) > 0 ) {
				foreach( $diff_arr as $field => $value ) {

					$old_value = NULL;
					if ( isset($old_data[$field]) ) {
						$old_value = $old_data[$field];
						if ( is_bool($old_value) AND $old_value === FALSE ) {
							$old_value = NULL;
						} elseif ( is_array($old_value) ) {
							//$old_value = serialize($old_value);
							//If the old value is an array, replace it with NULL because it will always match the NEW value too.
							$old_value = NULL;
						}
					}

					$new_value = $new_data[$field];
					if ( is_bool($new_value) AND $new_value === FALSE ) {
						$new_value = NULL;
					} elseif ( is_array($new_value) ) {
						$new_value = serialize($new_value);
					}

					//Debug::Text('Old Value: '. $old_value .' New Value: '. $new_value, __FILE__, __LINE__, __METHOD__, 10);
					if ( !($old_value == '' AND $new_value == '') ) {
						$ph[] = (int)$system_log_id;
						$ph[] = $field;
						$ph[] = $new_value;
						$ph[] = $old_value;
						$data[] = '(?,?,?,?)';
					}
				}

				if ( isset($data) ) {
					//Save data in a single SQL query.
					$query = 'INSERT INTO '. $this->getTable() .'(SYSTEM_LOG_ID,FIELD,NEW_VALUE,OLD_VALUE) VALUES'. implode(',', $data );
					//Debug::Text('Query: '. $query, __FILE__, __LINE__, __METHOD__, 10);
					$this->db->Execute($query, $ph);

					Debug::Text('Logged detail records in: '. (microtime(TRUE)-$start_time), __FILE__, __LINE__, __METHOD__, 10);

					return TRUE;
				}
			}
		}

		Debug::Text('Not logging detail records, likely no data changed in: '. (microtime(TRUE)-$start_time) .'s', __FILE__, __LINE__, __METHOD__, 10);
		return FALSE;
	}

	//This table doesn't have any of these columns, so overload the functions.
	function getDeleted() {
		return FALSE;
	}
	function setDeleted($bool) {
		return FALSE;
	}

	function getCreatedDate() {
		return FALSE;
	}
	function setCreatedDate($epoch = NULL) {
		return FALSE;
	}
	function getCreatedBy() {
		return FALSE;
	}
	function setCreatedBy($id = NULL) {
		return FALSE;
	}

	function getUpdatedDate() {
		return FALSE;
	}
	function setUpdatedDate($epoch = NULL) {
		return FALSE;
	}
	function getUpdatedBy() {
		return FALSE;
	}
	function setUpdatedBy($id = NULL) {
		return FALSE;
	}


	function getDeletedDate() {
		return FALSE;
	}
	function setDeletedDate($epoch = NULL) {
		return FALSE;
	}
	function getDeletedBy() {
		return FALSE;
	}
	function setDeletedBy($id = NULL) {
		return FALSE;
	}

	function preSave() {
		if ($this->getDate() === FALSE ) {
			$this->setDate();
		}

		return TRUE;
	}
}
?>
