TimeTrex/classes/modules/core/CustomFieldFactory.class.php

1355 lines
48 KiB
PHP
Raw Permalink Normal View History

2022-12-13 07:10:06 +01:00
<?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 );
}
}
?>