diff --git a/application/Espo/Core/FieldProcessing/EmailAddress/Saver.php b/application/Espo/Core/FieldProcessing/EmailAddress/Saver.php index 45aedfb8d9..5fd5cab361 100644 --- a/application/Espo/Core/FieldProcessing/EmailAddress/Saver.php +++ b/application/Espo/Core/FieldProcessing/EmailAddress/Saver.php @@ -145,14 +145,31 @@ class Saver implements SaverInterface $entity->hasFetched('emailAddressIsOptedOut') && $entity->get('emailAddressIsOptedOut') !== $entity->getFetched('emailAddressIsOptedOut') ) - ) + ) && + $emailAddressValue ) { - if ($emailAddressValue) { - $key = strtolower($emailAddressValue); + $key = strtolower($emailAddressValue); - if ($key && isset($hash->$key)) { - $hash->{$key}['optOut'] = $entity->get('emailAddressIsOptedOut'); - } + if ($key && isset($hash->$key)) { + $hash->{$key}['optOut'] = $entity->get('emailAddressIsOptedOut'); + } + } + + if ( + $entity->has('emailAddressIsInvalid') && + ( + $entity->isNew() || + ( + $entity->hasFetched('emailAddressIsInvalid') && + $entity->get('emailAddressIsInvalid') !== $entity->getFetched('emailAddressIsInvalid') + ) + ) && + $emailAddressValue + ) { + $key = strtolower($emailAddressValue); + + if ($key && isset($hash->$key)) { + $hash->{$key}['invalid'] = $entity->get('emailAddressIsInvalid'); } } @@ -421,6 +438,19 @@ class Saver implements SaverInterface $this->markAddressOptedOut($emailAddressValue, (bool) $entity->get('emailAddressIsOptedOut')); } + if ( + $entity->has('emailAddressIsInvalid') && + ( + $entity->isNew() || + ( + $entity->hasFetched('emailAddressIsInvalid') && + $entity->get('emailAddressIsInvalid') !== $entity->getFetched('emailAddressIsInvalid') + ) + ) + ) { + $this->markAddressInvalid($emailAddressValue, (bool) $entity->get('emailAddressIsInvalid')); + } + return; } @@ -444,6 +474,10 @@ class Saver implements SaverInterface $emailAddressNew->set('optOut', (bool) $entity->get('emailAddressIsOptedOut')); } + if ($entity->has('emailAddressIsInvalid')) { + $emailAddressNew->set('invalid', (bool) $entity->get('emailAddressIsInvalid')); + } + $this->entityManager->saveEntity($emailAddressNew); $isNewEmailAddress = true; @@ -469,6 +503,10 @@ class Saver implements SaverInterface $this->markAddressOptedOut($emailAddressValue, (bool) $entity->get('emailAddressIsOptedOut')); } + if ($entity->has('emailAddressIsInvalid')) { + $this->markAddressInvalid($emailAddressValue, (bool) $entity->get('emailAddressIsInvalid')); + } + $updateQuery = $this->entityManager ->getQueryBuilder() ->update() @@ -500,6 +538,14 @@ class Saver implements SaverInterface $repository->markAddressOptedOut($address, $isOptedOut); } + private function markAddressInvalid(string $address, bool $isInvalid = true): void + { + /** @var EmailAddressRepository $repository */ + $repository = $this->entityManager->getRepository(EmailAddress::ENTITY_TYPE); + + $repository->markAddressInvalid($address, $isInvalid); + } + private function checkChangeIsForbidden(EmailAddress $emailAddress, Entity $entity): bool { if (!$this->applicationState->hasUser()) { diff --git a/application/Espo/Core/FieldProcessing/PhoneNumber/Saver.php b/application/Espo/Core/FieldProcessing/PhoneNumber/Saver.php index ea8610e202..b078d732c4 100644 --- a/application/Espo/Core/FieldProcessing/PhoneNumber/Saver.php +++ b/application/Espo/Core/FieldProcessing/PhoneNumber/Saver.php @@ -150,24 +150,36 @@ class Saver implements SaverInterface } if ( - $entity->has('phoneNumberIsOptedOut') - && - ( - $entity->isNew() - || + $entity->has('phoneNumberIsOptedOut') && ( + $entity->isNew() || ( - $entity->hasFetched('phoneNumberIsOptedOut') - && + $entity->hasFetched('phoneNumberIsOptedOut') && $entity->get('phoneNumberIsOptedOut') !== $entity->getFetched('phoneNumberIsOptedOut') ) - ) + ) && + $phoneNumberValue ) { - if ($phoneNumberValue) { - $key = $phoneNumberValue; + $key = $phoneNumberValue; - if (isset($hash->$key)) { - $hash->{$key}['optOut'] = $entity->get('phoneNumberIsOptedOut'); - } + if (isset($hash->$key)) { + $hash->{$key}['optOut'] = $entity->get('phoneNumberIsOptedOut'); + } + } + + if ( + $entity->has('phoneNumberIsInvalid') && ( + $entity->isNew() || + ( + $entity->hasFetched('phoneNumberIsInvalid') && + $entity->get('phoneNumberIsInvalid') !== $entity->getFetched('phoneNumberIsInvalid') + ) + ) && + $phoneNumberValue + ) { + $key = $phoneNumberValue; + + if (isset($hash->$key)) { + $hash->{$key}['invalid'] = $entity->get('phoneNumberIsInvalid'); } } @@ -423,6 +435,10 @@ class Saver implements SaverInterface $phoneNumberNew->set('optOut', (bool) $entity->get('phoneNumberIsOptedOut')); } + if ($entity->has('phoneNumberIsInvalid')) { + $phoneNumberNew->set('invalid', (bool) $entity->get('phoneNumberIsInvalid')); + } + $defaultType = $this->metadata ->get('entityDefs.' . $entity->getEntityType() . '.fields.phoneNumber.defaultType'); @@ -453,6 +469,10 @@ class Saver implements SaverInterface $this->markNumberOptedOut($phoneNumberValue, (bool) $entity->get('phoneNumberIsOptedOut')); } + if ($entity->has('phoneNumberIsInvalid')) { + $this->markNumberInvalid($phoneNumberValue, (bool) $entity->get('phoneNumberIsInvalid')); + } + $update = $this->entityManager ->getQueryBuilder() ->update() @@ -472,21 +492,29 @@ class Saver implements SaverInterface } if ( - $entity->has('phoneNumberIsOptedOut') - && + $entity->has('phoneNumberIsOptedOut') && ( - $entity->isNew() - || + $entity->isNew() || ( - $entity->hasFetched('phoneNumberIsOptedOut') - && + $entity->hasFetched('phoneNumberIsOptedOut') && $entity->get('phoneNumberIsOptedOut') !== $entity->getFetched('phoneNumberIsOptedOut') ) ) ) { $this->markNumberOptedOut($phoneNumberValue, (bool) $entity->get('phoneNumberIsOptedOut')); + } - return; + if ( + $entity->has('phoneNumberIsInvalid') && + ( + $entity->isNew() || + ( + $entity->hasFetched('phoneNumberIsInvalid') && + $entity->get('phoneNumberIsInvalid') !== $entity->getFetched('phoneNumberIsInvalid') + ) + ) + ) { + $this->markNumberInvalid($phoneNumberValue, (bool) $entity->get('phoneNumberIsInvalid')); } return; @@ -521,6 +549,14 @@ class Saver implements SaverInterface $repository->markNumberOptedOut($number, $isOptedOut); } + private function markNumberInvalid(string $number, bool $isInvalid = true): void + { + /** @var PhoneNumberRepository $repository */ + $repository = $this->entityManager->getRepository(PhoneNumber::ENTITY_TYPE); + + $repository->markNumberInvalid($number, $isInvalid); + } + private function checkChangeIsForbidden(PhoneNumber $phoneNumber, Entity $entity): bool { if (!$this->applicationState->hasUser()) { diff --git a/application/Espo/Core/Utils/Database/Orm/Fields/Email.php b/application/Espo/Core/Utils/Database/Orm/Fields/Email.php index 566d70aa7e..1d3b742113 100644 --- a/application/Espo/Core/Utils/Database/Orm/Fields/Email.php +++ b/application/Espo/Core/Utils/Database/Orm/Fields/Email.php @@ -233,6 +233,61 @@ class Email extends Base 'additionalSelect' => ['emailAddresses.optOut'], ], ], + $fieldName .'IsInvalid' => [ + 'type' => 'bool', + 'notStorable' => true, + 'select' => [ + 'select' => "emailAddresses.invalid", + 'leftJoins' => [['emailAddresses', 'emailAddresses', ['primary' => 1]]], + ], + 'selectForeign' => [ + 'select' => "{$foreignJoinAlias}.invalid", + 'leftJoins' => [ + [ + 'EntityEmailAddress', + $foreignJoinMiddleAlias, + [ + "{$foreignJoinMiddleAlias}.entityId:" => "{alias}.id", + "{$foreignJoinMiddleAlias}.primary" => 1, + "{$foreignJoinMiddleAlias}.deleted" => 0, + ] + ], + [ + 'EmailAddress', + $foreignJoinAlias, + [ + "{$foreignJoinAlias}.id:" => "{$foreignJoinMiddleAlias}.emailAddressId", + "{$foreignJoinAlias}.deleted" => 0, + ] + ] + ], + ], + 'where' => [ + '= TRUE' => [ + 'whereClause' => [ + ['emailAddresses.invalid=' => true], + ['emailAddresses.invalid!=' => null], + ], + 'leftJoins' => [['emailAddresses', 'emailAddresses', ['primary' => 1]]], + ], + '= FALSE' => [ + 'whereClause' => [ + 'OR' => [ + ['emailAddresses.invalid=' => false], + ['emailAddresses.invalid=' => null], + ] + ], + 'leftJoins' => [['emailAddresses', 'emailAddresses', ['primary' => 1]]], + ] + ], + 'order' => [ + 'order' => [ + ['emailAddresses.invalid', '{direction}'], + ], + 'leftJoins' => [['emailAddresses', 'emailAddresses', ['primary' => 1]]], + 'additionalSelect' => ['emailAddresses.invalid'], + ], + ], ], 'relations' => [ 'emailAddresses' => [ diff --git a/application/Espo/Core/Utils/Database/Orm/Fields/Phone.php b/application/Espo/Core/Utils/Database/Orm/Fields/Phone.php index 87da8b4c5d..301f45d2a6 100644 --- a/application/Espo/Core/Utils/Database/Orm/Fields/Phone.php +++ b/application/Espo/Core/Utils/Database/Orm/Fields/Phone.php @@ -329,6 +329,61 @@ class Phone extends Base 'additionalSelect' => ['phoneNumbers.optOut'], ], ], + $fieldName .'IsInvalid' => [ + 'type' => 'bool', + 'notStorable' => true, + 'select' => [ + 'select' => 'phoneNumbers.invalid', + 'leftJoins' => [['phoneNumbers', 'phoneNumbers', ['primary' => 1]]], + ], + 'selectForeign' => [ + 'select' => "{$foreignJoinAlias}.invalid", + 'leftJoins' => [ + [ + 'EntityPhoneNumber', + $foreignJoinMiddleAlias, + [ + "{$foreignJoinMiddleAlias}.entityId:" => "{alias}.id", + "{$foreignJoinMiddleAlias}.primary" => 1, + "{$foreignJoinMiddleAlias}.deleted" => 0, + ] + ], + [ + 'PhoneNumber', + $foreignJoinAlias, + [ + "{$foreignJoinAlias}.id:" => "{$foreignJoinMiddleAlias}.phoneNumberId", + "{$foreignJoinAlias}.deleted" => 0, + ] + ] + ], + ], + 'where' => [ + '= TRUE' => [ + 'whereClause' => [ + ['phoneNumbers.invalid=' => true], + ['phoneNumbers.invalid!=' => null], + ], + 'leftJoins' => [['phoneNumbers', 'phoneNumbers', ['primary' => 1]]], + ], + '= FALSE' => [ + 'whereClause' => [ + 'OR' => [ + ['phoneNumbers.invalid=' => false], + ['phoneNumbers.invalid=' => null], + ] + ], + 'leftJoins' => [['phoneNumbers', 'phoneNumbers', ['primary' => 1]]], + ] + ], + 'order' => [ + 'order' => [ + ['phoneNumbers.invalid', '{direction}'], + ], + 'leftJoins' => [['phoneNumbers', 'phoneNumbers', ['primary' => 1]]], + 'additionalSelect' => ['phoneNumbers.invalid'], + ], + ], $fieldName . 'Numeric' => $numbericFieldDefs, ], 'relations' => [ diff --git a/application/Espo/Repositories/EmailAddress.php b/application/Espo/Repositories/EmailAddress.php index 608eae137c..86be5b13cc 100644 --- a/application/Espo/Repositories/EmailAddress.php +++ b/application/Espo/Repositories/EmailAddress.php @@ -376,4 +376,17 @@ class EmailAddress extends \Espo\Core\Repositories\Database implements $this->save($emailAddress); } + + public function markAddressInvalid(string $address, bool $isInvalid = true): void + { + $emailAddress = $this->getByAddress($address); + + if (!$emailAddress) { + return; + } + + $emailAddress->set('invalid', $isInvalid); + + $this->save($emailAddress); + } } diff --git a/application/Espo/Repositories/PhoneNumber.php b/application/Espo/Repositories/PhoneNumber.php index 0d74448b35..68679cb39b 100644 --- a/application/Espo/Repositories/PhoneNumber.php +++ b/application/Espo/Repositories/PhoneNumber.php @@ -280,4 +280,17 @@ class PhoneNumber extends Database implements $this->save($phoneNumber); } + + public function markNumberInvalid(string $number, bool $isInvalid = true): void + { + $phoneNumber = $this->getByNumber($number); + + if (!$phoneNumber) { + return; + } + + $phoneNumber->set('invalid', $isInvalid); + + $this->save($phoneNumber); + } } diff --git a/application/Espo/Resources/i18n/en_US/Global.json b/application/Espo/Resources/i18n/en_US/Global.json index cd8701b003..2faeabf2f0 100644 --- a/application/Espo/Resources/i18n/en_US/Global.json +++ b/application/Espo/Resources/i18n/en_US/Global.json @@ -403,6 +403,7 @@ "emailAddress": "Email", "emailAddressData": "Email Address Data", "emailAddressIsOptedOut": "Email Address is Opted-Out", + "emailAddressIsInvalid": "Email Address is Invalid", "assignedUserName": "Assigned User Name", "teams": "Teams", "createdAt": "Created At", @@ -419,6 +420,7 @@ "phoneNumberOther": "Phone (Other)", "phoneNumberData": "Phone Number Data", "phoneNumberIsOptedOut": "Phone Number is Opted-Out", + "phoneNumberIsInvalid": "Phone Number is Invalid", "order": "Order", "parent": "Parent", "children": "Children", diff --git a/application/Espo/Resources/metadata/fields/email.json b/application/Espo/Resources/metadata/fields/email.json index 198689cd3c..0a9d1173e5 100644 --- a/application/Espo/Resources/metadata/fields/email.json +++ b/application/Espo/Resources/metadata/fields/email.json @@ -12,6 +12,7 @@ ], "actualFields": [ "isOptedOut", + "isInvalid", "", "data" ], @@ -24,7 +25,18 @@ "layoutDefaultSidePanelDisabled": true, "mergeDisabled": true, "customizationDefaultDisabled": true, - "customizationReadOnlyDisabled": true + "customizationReadOnlyDisabled": true, + "customizationInlineEditDisabledDisabled": true + }, + "isInvalid": { + "type": "bool", + "notStorable": true, + "layoutDetailDisabled": true, + "layoutDefaultSidePanelDisabled": true, + "mergeDisabled": true, + "customizationDefaultDisabled": true, + "customizationReadOnlyDisabled": true, + "customizationInlineEditDisabledDisabled": true } }, "validationList": [ diff --git a/application/Espo/Resources/metadata/fields/phone.json b/application/Espo/Resources/metadata/fields/phone.json index 0e11bd282a..c647c1f54c 100644 --- a/application/Espo/Resources/metadata/fields/phone.json +++ b/application/Espo/Resources/metadata/fields/phone.json @@ -30,6 +30,7 @@ ], "actualFields": [ "isOptedOut", + "isInvalid", "", "data" ], @@ -42,7 +43,18 @@ "layoutDefaultSidePanelDisabled": true, "mergeDisabled": true, "customizationDefaultDisabled": true, - "customizationReadOnlyDisabled": true + "customizationReadOnlyDisabled": true, + "customizationInlineEditDisabledDisabled": true + }, + "isInvalid": { + "type": "bool", + "notStorable": true, + "layoutDetailDisabled": true, + "layoutDefaultSidePanelDisabled": true, + "mergeDisabled": true, + "customizationDefaultDisabled": true, + "customizationReadOnlyDisabled": true, + "customizationInlineEditDisabledDisabled": true } }, "validationList": [