<?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".
 *
 ********************************************************************************/


/**
 * @package Core
 */
class CustomFieldFactory extends Factory {
	protected $table = 'custom_field_control';
	protected $pk_sequence_name = 'custom_field_id_seq'; //PK Sequence name

	protected $company_obj = null;

	protected $dropdown_select_regex = '/^[a-zA-Z0-9_.:\-]+$/'; //Can't allow commas because of implode/explode on import/export for multiselect dropdowns. Import->parseCustomFieldOptions().

	protected $json_columns = [ 'meta_data', 'default_value' ];

	/**
	 * @param $name
	 * @param null $params
	 * @return array|null
	 */
	function _getFactoryOptions( $name, $params = null ) {

		$retval = null;
		switch ( $name ) {
			case 'status':
				$retval = [
						10 => TTi18n::gettext( 'ENABLED' ),
						20 => TTi18n::gettext( 'DISABLED' ),
				];
				break;
			case 'type':
			case 'type_id':
				$retval = [
						100 => TTi18n::gettext( 'Text' ),
				];

				if ( Misc::getCurrentCompanyProductEdition() >= TT_PRODUCT_PROFESSIONAL ) {
					$retval[110] = TTi18n::gettext( 'Textarea' ); //Password
					//$retval[120] = TTi18n::gettext( 'Hidden Text' ); //Password
					//$retval[180]  = TTi18n::gettext( 'Formula' );
					//$retval[190] = TTi18n::gettext( 'WYSIWYG' ); //HTML

					//$retval[200] = TTi18n::gettext( 'Tags' ); //Is this required?

					//$retval[300] = TTi18n::gettext( 'Link/URL' );

					$retval[400] = TTi18n::gettext( 'Integer' );  //Up to 64bit integer (long)
					$retval[410] = TTi18n::gettext( 'Decimal' );  //Variable precision (they chooose how many decimals max up to 10 decimal places)
					$retval[420] = TTi18n::gettext( 'Currency' ); //(Based on users currency. Variable decimal places, depending on record it's different.) Formats on output. User chooses specific currency they can have different decimal places?

					$retval[500] = TTi18n::gettext( 'Checkbox' );
					//$retval[600 = TTi18n::gettext( 'Radio' );

					//$retval[700 = TTi18n::gettext( 'Attachment' ); File Upload
					//$retval[800 = TTi18n::gettext( 'Image' );
					//$retval[900 = TTi18n::gettext( 'Color Picker' );

					$retval[1000] = TTi18n::gettext( 'Date' );
					$retval[1010] = TTi18n::gettext( 'Date Range' );

					$retval[1100] = TTi18n::gettext( 'Time' );
					//$retval[1110  = TTi18n::gettext( 'Time Range' );

					$retval[1200] = TTi18n::gettext( 'Datetime' );
					//$retval[1210 = TTi18n::gettext( 'Datetime Range' );

					$retval[1300] = TTi18n::gettext( 'Time Unit' );

					//$retval[2000]  = TTi18n::gettext( 'Single-select Dropdown (Simple)' ); //ComboBox
					//$retval[2010] = TTi18n::gettext( 'Multi-select Dropdown (Simple)' ); //ComboBox

					$retval[2100] = TTi18n::gettext( 'Single-select Dropdown' ); //AComboBox
					$retval[2110] = TTi18n::gettext( 'Multi-select Dropdown' );  //AComboBox

					//$retval[2200] = TTi18n::gettext( 'Dynamic Dropdown' ); //Links to existing list such as employee, job, department, etc.
				}

				//Punch control has different allowed types of custom fields, so we need to be able to filter them out based on product edition.
				if ( isset( $params['parent_table'] ) && $params['parent_table'] == 'punch_control' ) {
					if ( Misc::getCurrentCompanyProductEdition() <= TT_PRODUCT_PROFESSIONAL ) {
						//Professional Edition and lower only support text for punch control.
						$retval = array_intersect_key( $retval, [ 100 => true ] );
					} else {
						//Corporate edition allows for more punch control custom fields, but not all of them.
						unset(
								$retval[1000], //Date
								$retval[1010], //Date Range
								$retval[1100], //Time
								$retval[1200], //Datetime
						);
					}
				}
				break;
			case 'parent':
			case 'parent_table':
				$retval = [
						'company'      => TTi18n::gettext( 'Company' ),
						'branch'       => TTi18n::gettext( 'Branch' ),
						'department'   => TTi18n::gettext( 'Department' ),
						'ethnic_group' => TTi18n::gettext( 'Ethnicity' ),
						'users'        => TTi18n::gettext( 'Employee' ),
						'user_title'   => TTi18n::gettext( 'Employee Title' ),
						'user_contact' => TTi18n::gettext( 'Employee Contact' ),
						'schedule'     => TTi18n::gettext( 'Schedule' ),
						'legal_entity' => TTi18n::gettext( 'Legal Entities' ),
				];

				if ( Misc::getCurrentCompanyProductEdition() >= TT_PRODUCT_CORPORATE ) {
					$retval['punch_control'] = TTi18n::gettext( 'Punch' );
					$retval['job'] = TTi18n::gettext( 'Job' );
					$retval['job_item'] = TTi18n::gettext( 'Task' );
					$retval['client'] = TTi18n::gettext( 'Client' );
					$retval['client_contact'] = TTi18n::gettext( 'Client Contact' );
					$retval['product'] = TTi18n::gettext( 'Product' );
					$retval['invoice'] = TTi18n::gettext( 'Invoice' );
					$retval['document'] = TTi18n::gettext( 'Document' );
				} else {
					if ( Misc::getFeatureFlag( 'custom_field_punch' ) == true ) {
						$retval['punch_control'] = TTi18n::gettext( 'Punch' );
					}
				}

				if ( PRODUCTION == false ) {
					$retval['ui_kit'] = 'UI Kit Sample';
				}
				break;
			case 'legacy_type_to_parent_table':
				$retval = [
						2  => 'company',
						4  => 'branch',
						5  => 'department',
						10 => 'users',
						12 => 'user_title',
						15 => 'punch_control',
						18 => 'schedule',
						20 => 'job',
						30 => 'job_item',
						50 => 'client',
						55 => 'client_contact',
						60 => 'product',
						70 => 'invoice',
						80 => 'document',
				];
				break;
			case 'conversion_field_types':
				$retval = [
						500  => true,  //Checkbox
						1000 => true, //Date
						1010 => true, //Date range
						//1300 => true, //Time Unit
						2100 => true, //Single-select Dropdown
						2110 => true, //Multi-select Dropdown
				];
				break;
			case 'columns':
				$retval = [
						'-1010-status' => TTi18n::gettext( 'Status' ),
						'-1030-parent' => TTi18n::gettext( 'Object Type' ),
						'-1020-type'   => TTi18n::gettext( 'Field Type' ),
						'-1040-name'   => TTi18n::gettext( 'Name' ),

						'-2000-created_by'   => TTi18n::gettext( 'Created By' ),
						'-2010-created_date' => TTi18n::gettext( 'Created Date' ),
						'-2020-updated_by'   => TTi18n::gettext( 'Updated By' ),
						'-2030-updated_date' => TTi18n::gettext( 'Updated Date' ),
				];
				break;
			case 'list_columns':
				$retval = Misc::arrayIntersectByKey( $this->getOptions( 'default_display_columns' ), Misc::trimSortPrefix( $this->getOptions( 'columns' ) ) );
				break;
			case 'default_display_columns': //Columns that are displayed by default.
				$retval = [
						'status',
						'parent',
						'name',
						'type',
				];
				break;
		}

		return $retval;
	}

	/**
	 * @param $data
	 * @return array
	 */
	function _getVariableToFunctionMap( $data ) {
		$variable_function_map = [
				'id'                    => 'ID',
				'company_id'            => 'Company',
				'status_id'             => 'Status',
				'status'                => false,
				'type_id'               => 'Type',
				'type'                  => false,
				'parent_table'          => 'ParentTable',
				'parent'                => false,
				'name'                  => 'Name',
				'display_order'         => 'DisplayOrder',
				'default_value'         => 'DefaultValue',
				'is_required'           => 'IsRequired',
				'enable_search'         => 'EnableSearch',
				'meta_data'             => 'MetaData',
				'legacy_other_field_id' => 'LegacyOtherFieldId',
				'deleted'               => 'Deleted',
		];

		return $variable_function_map;
	}

	/**
	 * @return null
	 */
	function getCompanyObject() {
		if ( is_object( $this->company_obj ) ) {
			return $this->company_obj;
		} else {
			$clf = TTnew( 'CompanyListFactory' );
			/** @var CompanyListFactory $clf */
			$this->company_obj = $clf->getById( $this->getCompany() )->getCurrent();

			return $this->company_obj;
		}
	}

	/**
	 * @return bool|mixed
	 */
	function getCompany() {
		return $this->getGenericDataValue( 'company_id' );
	}

	/**
	 * @param string $value UUID
	 * @return bool
	 */
	function setCompany( $value ) {
		$value = TTUUID::castUUID( $value );

		return $this->setGenericDataValue( 'company_id', $value );
	}

	/**
	 * @return int
	 */
	function getStatus() {
		return $this->getGenericDataValue( 'status_id' );
	}

	/**
	 * @param $value
	 * @return bool
	 */
	function setStatus( $value ) {
		$value = (int)trim( $value );

		return $this->setGenericDataValue( 'status_id', $value );
	}

	/**
	 * @return bool|int
	 */
	function getType() {
		return $this->getGenericDataValue( 'type_id' );
	}

	/**
	 * @param $value
	 * @return bool
	 */
	function setType( $value ) {
		$value = (int)trim( $value );
		Debug::text( 'Attempting to set Type To: ' . $value, __FILE__, __LINE__, __METHOD__, 10 );

		return $this->setGenericDataValue( 'type_id', $value );
	}

	/**
	 * @return bool|string
	 */
	function getParentTable() {
		return $this->getGenericDataValue( 'parent_table' );
	}

	/**
	 * @param $value
	 * @return bool
	 */
	function setParentTable( $value ) {
		$value = trim( $value );
		return $this->setGenericDataValue( 'parent_table', $value );
	}


	/**
	 * @return bool|mixed
	 */
	function getName() {
		return $this->getGenericDataValue( 'name' );
	}

	/**
	 * @param $value
	 * @return bool
	 */
	function setName( $value ) {
		$value = trim( $value );

		return $this->setGenericDataValue( 'name', $value );
	}

	/**
	 * @return mixed
	 */
	function getDisplayOrder() {
		return $this->getGenericDataValue( 'display_order' );
	}

	/**
	 * @param $value
	 * @return bool
	 */
	function setDisplayOrder( $value ) {
		$value = trim( $value );

		return $this->setGenericDataValue( 'display_order', $value );
	}

	/**
	 * @return mixed
	 */
	function getDefaultValue() {
		$this->decodeJSONColumn( 'default_value' );

		$value = $this->getGenericDataValue( 'default_value' );

		return $value;
	}

	/**
	 * @param $value
	 * @return bool
	 */
	function setDefaultValue( $value ) {
		if ( is_array( $value ) == false ) {
			$value = trim( $value );
		}

		return $this->setGenericDataValue( 'default_value', $value );
	}

	/**
	 * @return bool
	 */
	function getIsRequired() {
		return $this->fromBool( $this->getGenericDataValue( 'is_required' ) );
	}

	/**
	 * @param $value
	 * @return bool
	 */
	function setIsRequired( $value ) {
		return $this->setGenericDataValue( 'is_required', $this->toBool( $value ) );
	}

	/**
	 * @return mixed
	 */
	function getEnableSearch() {
		return $this->fromBool( $this->getGenericDataValue( 'enable_search' ) );
	}

	/**
	 * @param $value
	 * @return bool
	 */
	function setEnableSearch( $value ) {
		return $this->setGenericDataValue( 'enable_search', $this->toBool( $value ) );
	}

	/**
	 * @return array
	 */
	function getMetaData() {
		$this->decodeJSONColumn( 'meta_data' );

		$value = $this->getGenericDataValue( 'meta_data' );

		if ( $value == false ) {
			return $this->getDefaultMetaData();
		}

		return $value;
	}

	/**
	 * @param $value
	 * @return bool
	 */
	function setMetaData( $value ) {
		return $this->setGenericDataValue( 'meta_data', $value );
	}

	/**
	 * @return bool|mixed
	 */
	function getLegacyOtherFieldId() {
		return $this->getGenericDataValue( 'legacy_other_field_id' );
	}

	/**
	 * @param $value
	 * @return bool
	 */
	function setLegacyOtherFieldId( $value ) {
		$value = trim( $value );

		return $this->setGenericDataValue( 'legacy_other_field_id', $value );
	}

	/**
	 * @return array
	 */
	function getDefaultMetaData() {
		return [
				'validation' => []
		];
	}

	/**
	 * Process data to correct formats (Date => ISO format: YYYY-MM-DD, etc) if required.
	 * @param int $type_id
	 * @return mixed
	 *
	 */
	function castToSQL( $type_id, $data ) {
		switch ( $type_id ) {
			case 400: //Integer
				if ( $data === null || is_bool( $data ) == true ) {
					$data = '';
				}
				$data = $data === '' ? $data : (int)$data; //We need to allow blank values for integers, in scenario where empty string becomes 0 and then fails validation.
				break;
			case 410: //Decimal
				if ( $data === null || is_bool( $data ) == true ) {
					$data = '';
				}
				$data = $data === '' ? $data : (float)$data;  //We need to allow blank values for decimals, in scenario where empty string becomes 0 and then fails validation.
				break;
			case 420: //Currency
				$data = (string)$data; //Cannot cast to float due to lack of precision.
				break;
			case 500: //Checkbox boolean
				$data = (bool)$data;
				break;
			case 1000: //Date ISO format: YYYY-MM-DD
				$data = TTDate::getISODateStamp( TTDate::parseDateTime( $data ) );
				break;
			case 1010: //Array of Date ISO format: YYYY-MM-DD
				if ( is_array( $data ) == false && strpos( $data, ' - ' ) !== false ) { //Range might be passed as a string or an array.
					$data = explode( ' - ', $data );
				}
				if ( is_array( $data ) ) {
					foreach ( $data as $key => $value ) {
						$data[$key] = TTDate::getISODateStamp( TTDate::parseDateTime( $value ) );
					}
				}
				break;
			case 1100: //ISO time format, 24hrs: 23:59:59
				$data = TTDate::getISOTime( TTDate::parseDateTime( $data ) );
				break;
			case 1200: //Epoch
				$data = TTDate::parseDateTime( $data );
				break;
			case 2100: //Single-select
				break; //No need to cast
			case 2110: //Multi-select
				if ( !is_array( $data ) ) {
					$data = [$data];
				}
				break;
			default:
				$data = (string)$data;
				break;
		}

		return $data;
	}


	/**
	 * @param $type_id
	 * @param $data
	 * @param $meta_data
	 * @param $human_readable
	 * @return mixed
	 */
	function castFromSQL( $type_id, $data, $meta_data = [], $human_readable = false ) {
		switch ( $type_id ) {
			case 400: //Integer
				if ( $data === null || is_bool( $data ) == true ) {
					$data = '';
				}
				$data = $data === '' ? $data : (int)$data; //We need to allow blank values for integers, in scenario where empty string becomes 0 and then fails validation.
				break;
			case 410: //Decimal
				if ( $data === null || is_bool( $data ) == true ) {
					$data = '';
				}
				$data = $data === '' ? $data : (float)$data;  //We need to allow blank values for decimals, in scenario where empty string becomes 0 and then fails validation.
				break;
			case 420: //Currency
				$data = (string)$data; //Cannot cast to float due to lack of precision.
				break;
			case 500: //Checkbox boolean
				$data = (bool)$data;
				if ( $human_readable == true ) {
					$data = Misc::HumanBoolean( $data );
				}
				break;
			case 1000:
				$data = TTDate::getDate( 'DATE', TTDate::parseDateTime( $data ) );
				break;
			case 1010:
				if ( is_array( $data ) ) {
					foreach ( $data as $key => $value ) {
						$data[$key] = TTDate::getDate( 'DATE', TTDate::parseDateTime( $value ) );
					}
				}

				if ( $human_readable && is_array( $data ) ) {
					$data = implode( ' - ', $data );
				}
				break;
			case 1100:
				$data = TTDate::getDate( 'TIME', TTDate::parseDateTime( $data ) );
				break;
			case 1200:
				$data = TTDate::getDate( 'DATE+TIME', $data );
				break;
			//case 1300: //Time Unit (This should always return seconds to avoid breaking reports)
				//if ( $human_readable == true ) {
				//	$data = TTDate::getTimeUnit( $data );
				//}
				//break;
			case 2100: //Single-select
			case 2110: //Multi-select
				if ( !is_array( $data ) ) {
					$data = [ $data ];
				}

				if ( $human_readable == true && isset( $meta_data['validation']['multi_select_items'] ) && is_array( $meta_data['validation']['multi_select_items'] ) ) {
					$multi_select_labels = [];
					foreach ( $data as $select_item ) {
						foreach ( $meta_data['validation']['multi_select_items'] as $multi_select_item ) {
							if ( $multi_select_item['value'] == $select_item ) {
								$multi_select_labels[] = $multi_select_item['label'];
								break;
							}
						}
					}
					$data = implode( ', ', $multi_select_labels );
				}
				break;
			default:
				$data = (string)$data;
				break;
		}

		return $data;
	}

	/**
	 * @return string
	 */
	function getPrefixedCustomFieldID( $use_parent_table = false ) {
		if ( $use_parent_table == true ) {
			return $this->getParentTable() . '_custom_field-' . $this->getId();
		} else {
			return 'custom_field-' . $this->getId();
		}
	}

	/**
	 * @param bool $ignore_warning
	 * @return bool
	 */
	function Validate( $ignore_warning = true ) {

		if ( $this->isNew() == true ) {
			$this->Validator->isTrue( 'status_id',
					( Misc::getCurrentCompanyProductEdition() >= TT_PRODUCT_PROFESSIONAL ),
									  TTi18n::gettext( 'Unable to create new custom fields, as this functionality has been deprecated in the Community Edition' ) );
		}

		// Company
		$clf = TTnew( 'CompanyListFactory' ); /** @var CompanyListFactory $clf */
		$this->Validator->isResultSetWithRows( 'company',
											   $clf->getByID( $this->getCompany() ),
											   TTi18n::gettext( 'Company is invalid' )
		);
		// Name
		$this->Validator->isLength( 'name',
									$this->getName(),
									TTi18n::gettext( 'Name is too short or too long' ),
									2,
									100
		);
		if ( $this->getDeleted() == false && $this->Validator->isError( 'name' ) == false ) {
			$this->Validator->isTrue( 'name',
									  $this->isUniqueName( $this->getName() ),
									  TTi18n::gettext( 'Name is already in use for this object type' )
			);
		}
		// Type
		if ( $this->getType() !== false ) {
			$this->Validator->inArrayKey( 'type_id',
										  $this->getType(),
										  TTi18n::gettext( 'Incorrect Type' ),
										  $this->getOptions( 'type_id' )
			);
		}

		//Limited types when parent table is punch
		if ( $this->getParentTable() === 'punch_control' ) {
			$this->Validator->isTrue( 'type_id',
									  in_array( $this->getType(), [
											  100, //Text
											  110, //Textarea
											  400, //Integer
											  410, //Decimal
											  420, //Currency
											  500, //Checkbox
											  1100, //Time
											  1300, //Time Unit
											  2100, //Single-select
											  2110, //Multi-select
									  ] ),
									  TTi18n::gettext( 'Incorrect Type for Punch' )
			);

			if ( $ignore_warning == false && $this->isNew() == true ) {
				$this->Validator->Warning( 'parent_table', TTi18n::gettext( 'To view this custom field, you must modify the permissions groups to allow "Punch -> Edit Custom Field (%1)" permissions', $this->getName() ) );
			}
		}

		// Status
		if ( $this->getStatus() != '' ) {
			$this->Validator->inArrayKey( 'status',
										  $this->getStatus(),
										  TTi18n::gettext( 'Incorrect Status' ),
										  $this->getOptions( 'status' )
			);
		}

		// Display
		if ( $this->getDisplayOrder() != '' ) {
			$this->Validator->isNumeric( 'display_order',
										 $this->getDisplayOrder(),
										 TTi18n::gettext( 'Invalid Display Order' )
			);
		}

		$data_diff = $this->getDataDifferences();
		if ( $this->isDataDifferent( 'type_id', $data_diff ) == true ) {
			$this->Validator->isTrue( 'type_id',
									  false,
									  TTi18n::gettext( 'Unable to change type of a custom field' ) );
		}

		if ( $this->isDataDifferent( 'parent_table', $data_diff ) == true ) {
			$this->Validator->isTrue( 'parent_table',
									  false,
									  TTi18n::gettext( 'Unable to change object type of a custom field' ) );
		}

		//  ***** Meta Data Validation Rules *****

		$meta_data = $this->getMetaData();
		if ( is_array( $meta_data ) && array_key_exists( 'validation', $meta_data ) ) {
			$validation_rules = $meta_data['validation'];
		} else {
			$validation_rules = [];
		}

		//Min Length
		if ( isset( $validation_rules[ 'validate_min_length'] ) && $validation_rules[ 'validate_min_length'] != '' ) {
			$this->Validator->isNumeric( 'validate_min_length',
										 $this->getMetaData()['validation']['validate_min_length'],
										 TTi18n::gettext( 'Minimum length must only be digits' )
			);
		}

		if ( isset( $validation_rules[ 'validate_decimal_places'] ) && $validation_rules[ 'validate_decimal_places'] != '' ) {
			$this->Validator->isNumeric( 'validate_decimal_places',
										 $this->getMetaData()['validation']['validate_decimal_places'],
										 TTi18n::gettext( 'Decimal places must only be digits' )
			);

			$this->Validator->isGreaterThan( 'validate_decimal_places',
											 $this->getMetaData()['validation']['validate_decimal_places'],
											 TTi18n::gettext( 'Minimum decimal places must be 1 or greater' ),
											 1
			);

			$this->Validator->isLessThan( 'validate_decimal_places',
											 $this->getMetaData()['validation']['validate_decimal_places'],
											 TTi18n::gettext( 'Minimum decimal places must be 8 or less' ),
											 8
			);
		}

		if ( isset( $validation_rules[ 'validate_min_length'] ) && $validation_rules[ 'validate_min_length'] != '' ) {
			$this->Validator->isGreaterThan( 'validate_min_length',
										 $this->getMetaData()['validation']['validate_min_length'],
										 TTi18n::gettext( 'Minimum length must be a positive value' ),
										 0
			);
		}

		//Max Length
		if ( isset( $validation_rules[ 'validate_max_length'] ) && $validation_rules[ 'validate_max_length'] !== '' && $validation_rules[ 'validate_max_length'] !== false ) {
			$this->Validator->isNumeric( 'validate_max_length',
										 $this->getMetaData()['validation']['validate_max_length'],
										 TTi18n::gettext( 'Maximum length must only be digits' )
			);
			$this->Validator->isGreaterThan( 'validate_max_length',
										  $this->getMetaData()['validation']['validate_max_length'],
										  TTi18n::gettext( 'Maximum length must be greater than minimum length' ),
										  $this->getMetaData()['validation']['validate_min_length']
			);
			$this->Validator->isGreaterThan( 'validate_max_length',
										  $this->getMetaData()['validation']['validate_max_length'],
										  TTi18n::gettext( 'Maximum length must be a positive value' ),
										  1
			);
		}

		//Min amount
		if ( isset( $validation_rules[ 'validate_min_amount'] ) && $validation_rules[ 'validate_min_amount'] != '' ) {
			$this->Validator->isNumeric( 'validate_min_amount',
										 $this->getMetaData()['validation']['validate_min_amount'],
										 TTi18n::gettext( 'Minimum amount must only be digits' )
			);
		}

		//Max amount
		if ( isset( $validation_rules[ 'validate_max_amount'] ) && $validation_rules[ 'validate_max_amount'] != '' ) {
			$this->Validator->isNumeric( 'validate_max_amount',
										 $this->getMetaData()['validation']['validate_max_amount'],
										 TTi18n::gettext( 'Maximum amount must only be digits' )
			);
			$this->Validator->isGreaterThan( 'validate_max_amount',
										  $this->getMetaData()['validation']['validate_max_amount'],
										  TTi18n::gettext( 'Maximum amount must be greater than minimum amount' ),
										  $this->getMetaData()['validation']['validate_min_amount']
			);
		}

		//Min time_unit
		if ( isset( $validation_rules[ 'validate_min_time_unit'] ) && $validation_rules[ 'validate_min_time_unit'] != '' ) {
			$this->Validator->isNumeric( 'validate_min_time_unit',
										 $this->getMetaData()['validation']['validate_min_time_unit'],
										 TTi18n::gettext( 'Minimum time unit must only be digits' )
			);
		}

		//Max time_unit
		if ( isset( $validation_rules[ 'validate_max_time_unit'] ) && $validation_rules[ 'validate_max_time_unit'] != '' && $validation_rules[ 'validate_min_time_unit'] != '' ) {
			$this->Validator->isNumeric( 'validate_max_time_unit',
										 $this->getMetaData()['validation']['validate_max_time_unit'],
										 TTi18n::gettext( 'Maximum time unit must only be digits' )
			);
			$this->Validator->isGreaterThan( 'validate_max_time_unit',
											 $this->getMetaData()['validation']['validate_max_time_unit'],
											 TTi18n::gettext( 'Maximum time unit must be greater than minimum time unit' ),
											 $this->getMetaData()['validation']['validate_min_time_unit']
			);
		}

		//Min date
		if ( isset( $validation_rules[ 'validate_min_date'] ) && $validation_rules[ 'validate_min_date'] != '' ) {
			$this->Validator->isDate( 'validate_min_date',
									  TTDate::parseDateTime( $this->getMetaData()['validation']['validate_min_date'] ),
									  TTi18n::gettext( 'Minimum date is not a valid date' )
			);
		}

		//Max date
		if ( isset( $validation_rules[ 'validate_max_date'] ) && $validation_rules[ 'validate_max_date'] != '' ) {
			$this->Validator->isDate( 'validate_max_date',
									  TTDate::parseDateTime( $this->getMetaData()['validation']['validate_max_date'] ),
									  TTi18n::gettext( 'Maximum date is not a valid date' )
			);
			$this->Validator->isGreaterThan( 'validate_max_date',
											 TTDate::parseDateTime( $this->getMetaData()['validation']['validate_max_date'] ),
											 TTi18n::gettext( 'Maximum date must be greater than minimum date' ),
											 TTDate::parseDateTime( $this->getMetaData()['validation']['validate_min_date'] )
			);
		}

		//Min datetime
		if ( isset( $validation_rules[ 'validate_min_datetime'] ) && $validation_rules[ 'validate_min_datetime'] != '' ) {
			$this->Validator->isDate( 'validate_min_datetime',
									  TTDate::parseDateTime( $this->getMetaData()['validation']['validate_min_datetime'] ),
									  TTi18n::gettext( 'Minimum date is not a valid date' )
			);
		}

		//Max datetime
		if ( isset( $validation_rules[ 'validate_max_datetime'] ) && $validation_rules[ 'validate_max_datetime'] != '' ) {
			$this->Validator->isDate( 'validate_max_datetime',
									  TTDate::parseDateTime( $this->getMetaData()['validation']['validate_max_datetime'] ),
									  TTi18n::gettext( 'Maximum date is not a valid date' )
			);
			$this->Validator->isGreaterThan( 'validate_max_datetime',
											 TTDate::parseDateTime( $this->getMetaData()['validation']['validate_max_datetime'] ),
											 TTi18n::gettext( 'Maximum date must be greater than minimum date' ),
											 TTDate::parseDateTime( $this->getMetaData()['validation']['validate_min_datetime'] )
			);
		}

		//Min time
		if ( isset( $validation_rules[ 'validate_min_time'] ) && $validation_rules[ 'validate_min_time'] != '' ) {
			$this->Validator->isDate( 'validate_min_time',
									  TTDate::parseDateTime( $this->getMetaData()['validation']['validate_min_time'] ),
									  TTi18n::gettext( 'Minimum time is not a valid time format' )
			);
		}

		//Max time
		if ( isset( $validation_rules[ 'validate_max_time'] ) && $validation_rules[ 'validate_max_time'] != '' ) {
			$this->Validator->isDate( 'validate_max_time',
									  TTDate::parseDateTime( $this->getMetaData()['validation']['validate_max_time'] ),
									  TTi18n::gettext( 'Maximum time is not a valid time format' )
			);
			$this->Validator->isGreaterThan( 'validate_max_time',
											 TTDate::parseDateTime( $this->getMetaData()['validation']['validate_max_time'] ),
											 TTi18n::gettext( 'Maximum time must be greater than minimum time' ),
											 TTDate::parseDateTime( $this->getMetaData()['validation']['validate_min_time'] )
			);
		}

		// Multi-select minimum amount of options selected
		if ( isset( $validation_rules[ 'validate_multi_select_min_amount'] ) && $validation_rules[ 'validate_multi_select_min_amount'] != '' ) {
			$this->Validator->isNumeric( 'validate_multi_select_min_amount',
										 $this->getMetaData()['validation']['validate_multi_select_min_amount'],
										 TTi18n::gettext( 'Invalid Multi-select Minimum' )
			);
		}

		// Multi-select maximum amount of options selected
		if ( isset( $validation_rules[ 'validate_multi_select_max_amount'] ) && $validation_rules[ 'validate_multi_select_max_amount'] != '' ) {
			$this->Validator->isNumeric( 'validate_multi_select_max_amount',
										 $this->getMetaData()['validation']['validate_multi_select_max_amount'],
										 TTi18n::gettext( 'Invalid Multi-select Maximum' )
			);
			$this->Validator->isGreaterThan( 'validate_multi_select_max_amount',
											 $this->getMetaData()['validation']['validate_multi_select_max_amount'],
											 TTi18n::gettext( 'Maximum amount must be greater than minimum amount' ),
											 $this->getMetaData()['validation']['validate_multi_select_min_amount']
			);
		}

		// Multi-select items
		if ( $this->Validator->getValidateOnly() == false && isset( $validation_rules['multi_select_items'] ) && ( $this->getType() == 2100 || $this->getType() == 2110 ) ) {
			foreach ( $this->getMetaData()['validation']['multi_select_items'] as $item ) {
				if ( $item['id'] !== TTUUID::getZeroID() ) {
					if ( trim( $item['value'] ) == '' || trim( $item['label'] ) == '' ) {
						$this->Validator->isTrue( 'multi_select_items',
												  false,
												  TTi18n::gettext( 'Invalid Multi-select item, value and display label cannot be blank' )
						);
						break;
					}
					$this->Validator->isRegEx( 'multi_select_items',
											   $item['value'],
											   TTi18n::gettext( 'Incorrect characters in select item value, must be only contain alpha numeric characters and ".", "_", ":", "-"' ),
											   $this->dropdown_select_regex
					);
				}
			}
			$this->Validator->isTrue( 'multi_select_items',
										empty( $this->getMetaData()['validation']['multi_select_items'] ) == false,
										TTi18n::gettext( 'Must create at least 1 select item' )
			);
			$this->Validator->isTrue( 'multi_select_items',
					( count( array_column( $this->getMetaData()['validation']['multi_select_items'], 'value' ) ) == count( array_unique( array_column( $this->getMetaData()['validation']['multi_select_items'], 'value' ) ) ) ),
									  TTi18n::gettext( 'Select item values must be unique' )
			);
			$this->Validator->isTrue( 'multi_select_items',
					( count( array_column( $this->getMetaData()['validation']['multi_select_items'], 'label' ) ) == count( array_unique( array_column( $this->getMetaData()['validation']['multi_select_items'], 'label' ) ) ) ),
									  TTi18n::gettext( 'Select item display labels must be unique' )
			);

		}

		$this->Validator->isTrue( 'legacy_other_id',
								  $this->isUniqeLegacyOtherId( $this->getLegacyOtherFieldId() ),
								  TTi18n::gettext( 'Legacy other field id is already in use' )
		);

		// Validate default_value against validation rules to help prevent impossible defaults
		$this->ValidateData( $this->getDefaultValue(), $this->Validator, 'default_value' );

		return true;
	}


	/**
	 * Validate custom field data or default_value against user created validation rules.
	 * @param $data
	 * @param Validator $validator
	 * @param bool $validation_field
	 * @return bool
	 */
	function ValidateData( $data, $validator, $validation_field = null ) {
		//Only validate default_value if the user entered a value. Otherwise, the user would be forced to create a default_value if they created validation rules.
		if ( $validation_field == 'default_value' && ( empty( $data ) == true || $data == TTUUID::getZeroID() ) ) {
			return true;
		}

		//Do not validate empty strings if the field is not required, such as an empty string value for an int field.
		if ( $this->getIsRequired() == false && $data === '' ) {
			return true;
		}

		if ( is_array( $this->getMetaData() ) && array_key_exists( 'validation', $this->getMetaData() ) ) {
			$validation_rules = $this->getMetaData()['validation'];
		} else {
			$validation_rules = [];
		}

		if ( $this->getIsRequired() == true && ( empty( $data ) == true || ( is_array( $data ) === false && $data === TTUUID::getZeroID() ) ) ) {
			$validator->isTrue( $validation_field ?? $this->getPrefixedCustomFieldID(),
								false,
								TTi18n::gettext( '%1 must be specified', $this->getName() ) );
		}

		//If a field is not specified at all based on the above "isTrue" validation check, don't bother checking other validation rules on it.
		if ( $validator->isError( $validation_field ?? $this->getPrefixedCustomFieldID() ) == false ) {
			if ( in_array( $this->getType(), [ 400, 410, 420 ] ) && $data != '' ) {
				$validator->isNumeric( $validation_field ?? $this->getPrefixedCustomFieldID(),
									   $data,
									   TTi18n::gettext( '%1 must be numeric', $this->getName() ) );
			}

			if ( $this->getType() == 1000 ) {
				if ( $data != '' ) {
					$validator->isDate( $validation_field ?? $this->getPrefixedCustomFieldID(),
										TTDate::parseDateTime( $data ),
										TTi18n::gettext( '%1 must be a valid date', $this->getName() ) );
				}

				//Min date
				if ( array_key_exists( 'validate_min_date', $validation_rules ) && $validation_rules['validate_min_date'] != '' ) {
					$validator->isGreaterThan( $validation_field ?? $this->getPrefixedCustomFieldID(),
											   TTDate::parseDateTime( $data ),
											   TTi18n::gettext( 'Must be between %1 and %2', [ TTDate::getDate( 'DATE', TTDate::parseDateTime( $validation_rules['validate_min_date'] ) ), TTDate::getDate( 'DATE', TTDate::parseDateTime( $validation_rules['validate_max_date'] ) ) ] ),
											   TTDate::parseDateTime( $validation_rules['validate_min_date'] )
					);
				}

				//Max date
				if ( array_key_exists( 'validate_max_date', $validation_rules ) && $validation_rules['validate_max_date'] != '' ) {
					$validator->isLessThan( $validation_field ?? $this->getPrefixedCustomFieldID(),
											TTDate::parseDateTime( $data ),
											TTi18n::gettext( 'Must be between %1 and %2', [ TTDate::getDate( 'DATE', TTDate::parseDateTime( $validation_rules['validate_min_date'] ) ), TTDate::getDate( 'DATE', TTDate::parseDateTime( $validation_rules['validate_max_date'] ) ) ] ),
											TTDate::parseDateTime( $validation_rules['validate_max_date'] )
					);
				}
			}

			if ( $this->getType() == 1010 && $data != '' ) {
				if ( is_string( $data ) && strpos( $data, ' - ' ) == false ) {
					$validator->isDate( $validation_field ?? $this->getPrefixedCustomFieldID(),
										TTDate::parseDateTime( $data ),
										TTi18n::gettext( '%1 must be a valid date range', $this->getName() ) );
				} else if ( array_key_exists( 'validate_min_date', $validation_rules ) && $validation_rules['validate_min_date'] != '' && $validation_rules['validate_max_date'] != '' ) {
					$dates = is_array( $data ) ? $data : explode( ' - ', $data );

					foreach ( $dates as $date ) {
						//Min date
						$min_result = $validator->isGreaterThan( $validation_field ?? $this->getPrefixedCustomFieldID(),
																 TTDate::parseDateTime( $date ),
																 TTi18n::gettext( 'Date range be between %1 and %2', [ TTDate::getDate( 'DATE', TTDate::parseDateTime( $validation_rules['validate_min_date'] ) ), TTDate::getDate( 'DATE', TTDate::parseDateTime( $validation_rules['validate_max_date'] ) ) ] ),
																 TTDate::parseDateTime( $validation_rules['validate_min_date'] )
						);

						if ( $min_result == false ) {
							break; //Only want to show one error if date range is invalid.
						}

						//Max date
						$max_result = $validator->isLessThan( $validation_field ?? $this->getPrefixedCustomFieldID(),
															  TTDate::parseDateTime( $date ),
															  TTi18n::gettext( 'Date range must be between %1 and %2', [ TTDate::getDate( 'DATE', TTDate::parseDateTime( $validation_rules['validate_min_date'] ) ), TTDate::getDate( 'DATE', TTDate::parseDateTime( $validation_rules['validate_max_date'] ) ) ] ),
															  TTDate::parseDateTime( $validation_rules['validate_max_date'] )
						);

						if ( $max_result == false ) {
							break; //Only want to show one error if date range is invalid.
						}
					}
				}
			}

			if ( $this->getType() == 1200 ) {
				$validator->isDate( $validation_field ?? $this->getPrefixedCustomFieldID(),
									TTDate::parseDateTime( $data ),
									TTi18n::gettext( '%1 must be a valid date/time', $this->getName() ) );
			}

			//Min and Max Length
			if ( array_key_exists( 'validate_min_length', $validation_rules ) && $validation_rules['validate_min_length'] != '' && $validation_rules['validate_max_length'] != '' ) {
				$validator->isGreaterThan( $validation_field ?? $this->getPrefixedCustomFieldID(),
										   strlen( $data ),
										   TTi18n::gettext( 'Must be %1 or more characters', [ $validation_rules['validate_min_length'] ] ),
										   $validation_rules['validate_min_length']
				);

				$validator->isLessThan( $validation_field ?? $this->getPrefixedCustomFieldID(),
										strlen( $data ),
										TTi18n::gettext( 'Must be %1 or less characters', [ $validation_rules['validate_max_length'] ] ),
										$validation_rules['validate_max_length']
				);
			}

			//Min amount
			if ( array_key_exists( 'validate_min_amount', $validation_rules ) && $validation_rules['validate_min_amount'] != '' ) {
				//If validation rules are set to validate decimal places, round the data to that number of decimal places.
				if ( array_key_exists( 'validate_decimal_places', $validation_rules ) && $validation_rules['validate_decimal_places'] != '' ) {
					$amount = round( $data, $validation_rules['validate_decimal_places'] );
				} else {
					$amount = $data;
				}
				$validator->isGreaterThan( $validation_field ?? $this->getPrefixedCustomFieldID(),
										   $amount,
										   TTi18n::gettext( 'Must be %1 or greater', [ $validation_rules['validate_min_amount'] ] ),
										   $validation_rules['validate_min_amount']
				);
			}

			//Max amount
			if ( array_key_exists( 'validate_max_amount', $validation_rules ) && $validation_rules['validate_max_amount'] != '' ) {
				//If validation rules are set to validate decimal places, round the data to that number of decimal places.
				if ( array_key_exists( 'validate_decimal_places', $validation_rules ) && $validation_rules['validate_decimal_places'] != '' ) {
					$amount = round( $data, $validation_rules['validate_decimal_places'] );
				} else {
					$amount = $data;
				}

				$validator->isLessThan( $validation_field ?? $this->getPrefixedCustomFieldID(),
										$amount,
										TTi18n::gettext( 'Must be %1 or less', [ $validation_rules['validate_max_amount'] ] ),
										$validation_rules['validate_max_amount']
				);
			}

			//Min time_unit
			if ( array_key_exists( 'validate_min_time_unit', $validation_rules ) && $validation_rules['validate_min_time_unit'] != '' ) {
				$validator->isGreaterThan( $validation_field ?? $this->getPrefixedCustomFieldID(),
										   $data,
										   TTi18n::gettext( 'Must be %1 or greater', [ TTDate::convertSecondsToHMS( $validation_rules['validate_min_time_unit'] ) ] ),
										   $validation_rules['validate_min_time_unit']
				);
			}

			//Max time_unit
			if ( array_key_exists( 'validate_max_time_unit', $validation_rules ) && $validation_rules['validate_max_time_unit'] != '' ) {
				$validator->isLessThan( $validation_field ?? $this->getPrefixedCustomFieldID(),
										$data,
										TTi18n::gettext( 'Must be %1 or less', [ TTDate::convertSecondsToHMS( $validation_rules['validate_max_time_unit'] ) ] ),
										$validation_rules['validate_max_time_unit']
				);
			}

			//Min datetime
			if ( array_key_exists( 'validate_min_datetime', $validation_rules ) && $validation_rules['validate_min_datetime'] != '' ) {
				$validator->isGreaterThan( $validation_field ?? $this->getPrefixedCustomFieldID(),
										   TTDate::parseDateTime( $data ),
										   TTi18n::gettext( 'Must be between %1 and %2', [ TTDate::getDate( 'DATE+TIME', TTDate::parseDateTime( $validation_rules['validate_min_datetime'] ) ), TTDate::getDate( 'DATE+TIME', TTDate::parseDateTime( $validation_rules['validate_max_datetime'] ) ) ] ),
										   TTDate::parseDateTime( $validation_rules['validate_min_datetime'] )
				);
			}

			//Max datetime
			if ( array_key_exists( 'validate_max_datetime', $validation_rules ) && $validation_rules['validate_max_datetime'] != '' ) {
				$validator->isLessThan( $validation_field ?? $this->getPrefixedCustomFieldID(),
										TTDate::parseDateTime( $data ),
										TTi18n::gettext( 'Must be between %1 and %2', [ TTDate::getDate( 'DATE+TIME', TTDate::parseDateTime( $validation_rules['validate_min_datetime'] ) ), TTDate::getDate( 'DATE+TIME', TTDate::parseDateTime( $validation_rules['validate_max_datetime'] ) ) ] ),
										TTDate::parseDateTime( $validation_rules['validate_max_datetime'] )
				);
			}

			//Min time
			if ( array_key_exists( 'validate_min_time', $validation_rules ) && $validation_rules['validate_min_time'] != '' ) {
				$validator->isGreaterThan( $validation_field ?? $this->getPrefixedCustomFieldID(),
										   TTDate::parseDateTime( $data ),
										   TTi18n::gettext( 'Must be between %1 and %2', [ TTDate::getDate( 'TIME', TTDate::parseDateTime( $validation_rules['validate_min_time'] ) ), TTDate::getDate( 'TIME', TTDate::parseDateTime( $validation_rules['validate_max_time'] ) ) ] ),
										   TTDate::parseDateTime( $validation_rules['validate_min_time'] )
				);
			}

			//Max time
			if ( array_key_exists( 'validate_max_time', $validation_rules ) && $validation_rules['validate_max_time'] != '' ) {
				$validator->isLessThan( $validation_field ?? $this->getPrefixedCustomFieldID(),
										TTDate::parseDateTime( $data ),
										TTi18n::gettext( 'Must be between %1 and %2', [ TTDate::getDate( 'TIME', TTDate::parseDateTime( $validation_rules['validate_min_time'] ) ), TTDate::getDate( 'TIME', TTDate::parseDateTime( $validation_rules['validate_max_time'] ) ) ] ),
										TTDate::parseDateTime( $validation_rules['validate_max_time'] )
				);
			}

			//Min amount
			if ( array_key_exists( 'validate_multi_select_min_amount', $validation_rules ) && $validation_rules['validate_multi_select_min_amount'] != '' ) {
				$validator->isGreaterThan( $validation_field ?? $this->getPrefixedCustomFieldID(),
										   count( is_array( $data ) ? $data : [] ),
										   TTi18n::gettext( 'Must select more than %1', [ $validation_rules['validate_multi_select_min_amount'] ] ),
										   $validation_rules['validate_multi_select_min_amount']
				);
			}

			//Max amount
			if ( array_key_exists( 'validate_multi_select_max_amount', $validation_rules ) && $validation_rules['validate_multi_select_max_amount'] != '' ) {
				$validator->isLessThan( $validation_field ?? $this->getPrefixedCustomFieldID(),
										count( is_array( $data ) ? $data : [] ),
										TTi18n::gettext( 'Must select %1 or less items', [ $validation_rules['validate_multi_select_max_amount'] ] ),
										$validation_rules['validate_multi_select_max_amount']
				);
			}
		}

		return true;
	}

	/**
	 * @param string $id UUID
	 * @return bool
	 */
	function isUniqeLegacyOtherId( $id ) {
		$ph = [
				'company_id'            => TTUUID::castUUID( $this->getCompany() ),
				'parent_table'          => $this->getParentTable(),
				'legacy_other_field_id' => (string)$id,
		];

		//get next legacy_id and make sure does not exist

		$query = 'SELECT legacy_other_field_id FROM ' . $this->getTable() . ' WHERE company_id = ? AND parent_table = ? AND legacy_other_field_id = ?';
		$legacy_other_id = $this->db->GetOne( $query, $ph );
		Debug::Arr( $legacy_other_id, 'Unique Legacy ID: ' . $id, __FILE__, __LINE__, __METHOD__, 10 );

		if ( $legacy_other_id === false ) {
			return true;
		} else {
			if ( $legacy_other_id == $this->getLegacyOtherFieldId() ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * @return int
	 */
	function generateLegacyOtherId() {
		$ph = [
				'company_id'            => TTUUID::castUUID( $this->getCompany() ),
				'parent_table'          => $this->getParentTable()
		];

		//Get the highest existing legacy_other_id for this parent_table and increment it by 1.
		$query = 'SELECT MAX(legacy_other_field_id) as max_id FROM ' . $this->getTable() . ' WHERE company_id = ? AND parent_table = ?';

		$result = $this->db->GetOne( $query, $ph );
		if ( $result == null ) {
			return 1;
		}

		return (int)$result + 1;
	}

	/**
	 * @param $name
	 * @return bool
	 */
	function isUniqueName( $name ) {
		$ph = [
				'company_id'   => TTUUID::castUUID( $this->getCompany() ),
				'name'         => TTi18n::strtolower( trim( $name ) ),
				'parent_table' => $this->getParentTable(),
		];

		$query = 'select id from ' . $this->getTable() . ' where company_id = ? AND lower(name) = ? AND parent_table = ? AND deleted = 0';
		$name_id = $this->db->GetOne( $query, $ph );
		Debug::Arr( $name_id, 'Unique Name: ' . $name, __FILE__, __LINE__, __METHOD__, 10 );

		if ( $name_id === false ) {
			return true;
		} else {
			if ( $name_id == $this->getId() ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * @return bool
	 */
	function preValidate() {
		$default_value = $this->getDefaultValue();

		if ( empty( $default_value ) == false ) {
			$this->setDefaultValue( $this->castToSQL( $this->getType(), $default_value ) );
		}

		if ( $this->isNew() ) {
			$this->setLegacyOtherFieldId( $this->generateLegacyOtherId() );
		}

		return true;
	}

	/**
	 * @return bool
	 */
	function postSave() {
		//Remove cache for custom field by parent table and company
		$this->removeCache( 'custom_field-' . $this->getCompany() . $this->getParentTable() );
		$this->removeCache( 'custom_field-' . $this->getCompany() );

		return true;
	}

	/**
	 * @param $data
	 * @return bool
	 */
	function setObjectFromArray( $data ) {
		if ( is_array( $data ) ) {
			$variable_function_map = $this->getVariableToFunctionMap();
			foreach ( $variable_function_map as $key => $function ) {
				if ( isset( $data[$key] ) ) {

					$function = 'set' . $function;
					switch ( $key ) {
						default:
							if ( method_exists( $this, $function ) ) {
								$this->$function( $data[$key] );
							}
							break;
					}
				}
			}

			$this->setCreatedAndUpdatedColumns( $data );

			return true;
		}

		return false;
	}

	/**
	 * @param null $include_columns
	 * @return array
	 */
	function getObjectAsArray( $include_columns = null ) {
		$data = [];
		$variable_function_map = $this->getVariableToFunctionMap();
		if ( is_array( $variable_function_map ) ) {
			foreach ( $variable_function_map as $variable => $function_stub ) {
				if ( $include_columns == null || ( isset( $include_columns[$variable] ) && $include_columns[$variable] == true ) ) {

					$function = 'get' . $function_stub;
					switch ( $variable ) {
						case 'type':
							$data[$variable] = Option::getByKey( $this->getType(), $this->getOptions( $variable ) );
							break;
						case 'parent':
							$data[$variable] = Option::getByKey( $this->getParentTable(), $this->getOptions( $variable ) );
							break;
						case 'status':
							$data[$variable] = Option::getByKey( $this->getStatus(), $this->getOptions( $variable ) );
							break;
						default:
							if ( method_exists( $this, $function ) ) {
								$data[$variable] = $this->$function();
							}
							break;
					}
				}
			}
			$this->getCreatedAndUpdatedColumns( $data, $include_columns );
		}

		return $data;
	}

	/**
	 * @param $log_action
	 * @return bool
	 */
	function addLog( $log_action ) {
		return TTLog::addEntry( $this->getId(), $log_action, TTi18n::getText( 'Custom Fields' ), null, $this->getTable(), $this );
	}
}

?>