Initial commit
This commit is contained in:
197
application/Espo/Core/Field/Address.php
Normal file
197
application/Espo/Core/Field/Address.php
Normal file
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field;
|
||||
|
||||
use Espo\Core\Field\Address\AddressBuilder;
|
||||
|
||||
/**
|
||||
* An address value object. Immutable.
|
||||
*/
|
||||
class Address
|
||||
{
|
||||
public function __construct(
|
||||
private ?string $country = null,
|
||||
private ?string $state = null,
|
||||
private ?string $city = null,
|
||||
private ?string $street = null,
|
||||
private ?string $postalCode = null
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Whether has a street.
|
||||
*/
|
||||
public function hasStreet(): bool
|
||||
{
|
||||
return $this->street !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether has a city.
|
||||
*/
|
||||
public function hasCity(): bool
|
||||
{
|
||||
return $this->city !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether has a country.
|
||||
*/
|
||||
public function hasCountry(): bool
|
||||
{
|
||||
return $this->country !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether has a state.
|
||||
*/
|
||||
public function hasState(): bool
|
||||
{
|
||||
return $this->state !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether has a postal code.
|
||||
*/
|
||||
public function hasPostalCode(): bool
|
||||
{
|
||||
return $this->postalCode !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a street.
|
||||
*/
|
||||
public function getStreet(): ?string
|
||||
{
|
||||
return $this->street;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a city.
|
||||
*/
|
||||
public function getCity(): ?string
|
||||
{
|
||||
return $this->city;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a country.
|
||||
*/
|
||||
public function getCountry(): ?string
|
||||
{
|
||||
return $this->country;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a state.
|
||||
*/
|
||||
public function getState(): ?string
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a postal code.
|
||||
*/
|
||||
public function getPostalCode(): ?string
|
||||
{
|
||||
return $this->postalCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a street.
|
||||
*/
|
||||
public function withStreet(?string $street): self
|
||||
{
|
||||
return self::createBuilder()
|
||||
->clone($this)
|
||||
->setStreet($street)
|
||||
->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a city.
|
||||
*/
|
||||
public function withCity(?string $city): self
|
||||
{
|
||||
return self::createBuilder()
|
||||
->clone($this)
|
||||
->setCity($city)
|
||||
->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a country.
|
||||
*/
|
||||
public function withCountry(?string $country): self
|
||||
{
|
||||
return self::createBuilder()
|
||||
->clone($this)
|
||||
->setCountry($country)
|
||||
->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a state.
|
||||
*/
|
||||
public function withState(?string $state): self
|
||||
{
|
||||
return self::createBuilder()
|
||||
->clone($this)
|
||||
->setState($state)
|
||||
->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a postal code.
|
||||
*/
|
||||
public function withPostalCode(?string $postalCode): self
|
||||
{
|
||||
return self::createBuilder()
|
||||
->clone($this)
|
||||
->setPostalCode($postalCode)
|
||||
->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an empty address.
|
||||
*/
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a builder.
|
||||
*/
|
||||
public static function createBuilder(): AddressBuilder
|
||||
{
|
||||
return new AddressBuilder();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\Address;
|
||||
|
||||
use Espo\ORM\Value\AttributeExtractor;
|
||||
|
||||
use Espo\Core\Field\Address;
|
||||
|
||||
use stdClass;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @implements AttributeExtractor<Address>
|
||||
*/
|
||||
class AddressAttributeExtractor implements AttributeExtractor
|
||||
{
|
||||
public function extract(object $value, string $field): stdClass
|
||||
{
|
||||
if (!$value instanceof Address) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
return (object) [
|
||||
$field . 'Street' => $value->getStreet(),
|
||||
$field . 'City' => $value->getCity(),
|
||||
$field . 'Country' => $value->getCountry(),
|
||||
$field . 'State' => $value->getState(),
|
||||
$field . 'PostalCode' => $value->getPostalCode(),
|
||||
];
|
||||
}
|
||||
|
||||
public function extractFromNull(string $field): stdClass
|
||||
{
|
||||
return (object) [
|
||||
$field . 'Street' => null,
|
||||
$field . 'City' => null,
|
||||
$field . 'Country' => null,
|
||||
$field . 'State' => null,
|
||||
$field . 'PostalCode' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
105
application/Espo/Core/Field/Address/AddressBuilder.php
Normal file
105
application/Espo/Core/Field/Address/AddressBuilder.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\Address;
|
||||
|
||||
use Espo\Core\Field\Address;
|
||||
|
||||
/**
|
||||
* An address value builder.
|
||||
*/
|
||||
class AddressBuilder
|
||||
{
|
||||
private ?string $street;
|
||||
|
||||
private ?string $city;
|
||||
|
||||
private ?string $country;
|
||||
|
||||
private ?string $state;
|
||||
|
||||
private ?string $postalCode;
|
||||
|
||||
public function clone(Address $address): self
|
||||
{
|
||||
$this->setStreet($address->getStreet());
|
||||
$this->setCity($address->getCity());
|
||||
$this->setCountry($address->getCountry());
|
||||
$this->setState($address->getState());
|
||||
$this->setPostalCode($address->getPostalCode());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setStreet(?string $street): self
|
||||
{
|
||||
$this->street = $street;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCity(?string $city): self
|
||||
{
|
||||
$this->city = $city;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCountry(?string $country): self
|
||||
{
|
||||
$this->country = $country;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setState(?string $state): self
|
||||
{
|
||||
$this->state = $state;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPostalCode(?string $postalCode): self
|
||||
{
|
||||
$this->postalCode = $postalCode;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function build(): Address
|
||||
{
|
||||
return new Address(
|
||||
$this->country,
|
||||
$this->state,
|
||||
$this->city,
|
||||
$this->street,
|
||||
$this->postalCode
|
||||
);
|
||||
}
|
||||
}
|
||||
54
application/Espo/Core/Field/Address/AddressFactory.php
Normal file
54
application/Espo/Core/Field/Address/AddressFactory.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\Address;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\Value\ValueFactory;
|
||||
|
||||
use Espo\Core\Field\Address;
|
||||
|
||||
class AddressFactory implements ValueFactory
|
||||
{
|
||||
public function isCreatableFromEntity(Entity $entity, string $field): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function createFromEntity(Entity $entity, string $field): Address
|
||||
{
|
||||
return (new AddressBuilder())
|
||||
->setStreet($entity->get($field . 'Street'))
|
||||
->setCity($entity->get($field . 'City'))
|
||||
->setCountry($entity->get($field . 'Country'))
|
||||
->setState($entity->get($field . 'State'))
|
||||
->setPostalCode($entity->get($field . 'PostalCode'))
|
||||
->build();
|
||||
}
|
||||
}
|
||||
40
application/Espo/Core/Field/Address/AddressFormatter.php
Normal file
40
application/Espo/Core/Field/Address/AddressFormatter.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\Address;
|
||||
|
||||
use Espo\Core\Field\Address;
|
||||
|
||||
/**
|
||||
* An address formatter.
|
||||
*/
|
||||
interface AddressFormatter
|
||||
{
|
||||
public function format(Address $address): string;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\Address;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Config;
|
||||
|
||||
class AddressFormatterFactory
|
||||
{
|
||||
public function __construct(
|
||||
private AddressFormatterMetadataProvider $metadataProvider,
|
||||
private InjectableFactory $injectableFactory,
|
||||
private Config $config
|
||||
) {}
|
||||
|
||||
public function create(int $format): AddressFormatter
|
||||
{
|
||||
/** @var ?class-string<AddressFormatter> $className */
|
||||
$className = $this->metadataProvider->getFormatterClassName($format);
|
||||
|
||||
if (!$className) {
|
||||
throw new RuntimeException("Unknown address format '{$format}'.");
|
||||
}
|
||||
|
||||
return $this->injectableFactory->create($className);
|
||||
}
|
||||
|
||||
public function createDefault(): AddressFormatter
|
||||
{
|
||||
$format = $this->config->get('addressFormat') ?? 1;
|
||||
|
||||
return $this->create($format);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\Address;
|
||||
|
||||
use Espo\Core\Utils\Metadata;
|
||||
|
||||
class AddressFormatterMetadataProvider
|
||||
{
|
||||
private $metadata;
|
||||
|
||||
public function __construct(Metadata $metadata)
|
||||
{
|
||||
$this->metadata = $metadata;
|
||||
}
|
||||
|
||||
public function getFormatterClassName(int $format): ?string
|
||||
{
|
||||
return $this->metadata->get([
|
||||
'app', 'addressFormats', strval($format), 'formatterClassName',
|
||||
]);
|
||||
}
|
||||
}
|
||||
208
application/Espo/Core/Field/Currency.php
Normal file
208
application/Espo/Core/Field/Currency.php
Normal file
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field;
|
||||
|
||||
use Espo\Core\Currency\CalculatorUtil;
|
||||
|
||||
use RuntimeException;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* A currency value object. Immutable.
|
||||
*/
|
||||
class Currency
|
||||
{
|
||||
/** @var numeric-string */
|
||||
private string $amount;
|
||||
private string $code;
|
||||
|
||||
/**
|
||||
* @param numeric-string|float|int $amount An amount.
|
||||
* @param string $code A currency code.
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function __construct($amount, string $code)
|
||||
{
|
||||
if (!is_string($amount) && !is_float($amount) && !is_int($amount)) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
if (strlen($code) !== 3) {
|
||||
throw new RuntimeException("Bad currency code.");
|
||||
}
|
||||
|
||||
if (is_float($amount) || is_int($amount)) {
|
||||
$amount = (string) $amount;
|
||||
}
|
||||
|
||||
$this->amount = $amount;
|
||||
$this->code = $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an amount as string.
|
||||
*
|
||||
* @return numeric-string
|
||||
*/
|
||||
public function getAmountAsString(): string
|
||||
{
|
||||
return $this->amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an amount.
|
||||
*/
|
||||
public function getAmount(): float
|
||||
{
|
||||
return (float) $this->amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a currency code.
|
||||
*/
|
||||
public function getCode(): string
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a currency value.
|
||||
*
|
||||
* @throws RuntimeException If currency codes are different.
|
||||
*/
|
||||
public function add(self $value): self
|
||||
{
|
||||
if ($this->getCode() !== $value->getCode()) {
|
||||
throw new RuntimeException("Can't add a currency value with a different code.");
|
||||
}
|
||||
|
||||
$amount = CalculatorUtil::add(
|
||||
$this->getAmountAsString(),
|
||||
$value->getAmountAsString()
|
||||
);
|
||||
|
||||
return new self($amount, $this->getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtract a currency value.
|
||||
*
|
||||
* @throws RuntimeException If currency codes are different.
|
||||
*/
|
||||
public function subtract(self $value): self
|
||||
{
|
||||
if ($this->getCode() !== $value->getCode()) {
|
||||
throw new RuntimeException("Can't subtract a currency value with a different code.");
|
||||
}
|
||||
|
||||
$amount = CalculatorUtil::subtract(
|
||||
$this->getAmountAsString(),
|
||||
$value->getAmountAsString()
|
||||
);
|
||||
|
||||
return new self($amount, $this->getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiply by a multiplier.
|
||||
*/
|
||||
public function multiply(float|int $multiplier): self
|
||||
{
|
||||
$amount = CalculatorUtil::multiply(
|
||||
$this->getAmountAsString(),
|
||||
(string) $multiplier
|
||||
);
|
||||
|
||||
return new self($amount, $this->getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Divide by a divider.
|
||||
*/
|
||||
public function divide(float|int $divider): self
|
||||
{
|
||||
$amount = CalculatorUtil::divide(
|
||||
$this->getAmountAsString(),
|
||||
(string) $divider
|
||||
);
|
||||
|
||||
return new self($amount, $this->getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Round with a precision.
|
||||
*/
|
||||
public function round(int $precision = 0): self
|
||||
{
|
||||
$amount = CalculatorUtil::round($this->getAmountAsString(), $precision);
|
||||
|
||||
return new self($amount, $this->getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare with another currency value. Returns:
|
||||
* - `1` if greater than the value;
|
||||
* - `0` if equal to the value;
|
||||
* - `-1` if less than the value.
|
||||
*
|
||||
* @throws RuntimeException If currency codes are different.
|
||||
*/
|
||||
public function compare(self $value): int
|
||||
{
|
||||
if ($this->getCode() !== $value->getCode()) {
|
||||
throw new RuntimeException("Can't compare currencies with different codes.");
|
||||
}
|
||||
|
||||
return CalculatorUtil::compare(
|
||||
$this->getAmountAsString(),
|
||||
$value->getAmountAsString()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the value is negative.
|
||||
*/
|
||||
public function isNegative(): bool
|
||||
{
|
||||
return $this->compare(self::create(0.0, $this->code)) === -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from an amount and code.
|
||||
*
|
||||
* @param numeric-string|float|int $amount An amount.
|
||||
* @param string $code A currency code.
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function create($amount, string $code): self
|
||||
{
|
||||
return new self($amount, $code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\Currency;
|
||||
|
||||
use Espo\ORM\Defs;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\Value\AttributeExtractor;
|
||||
|
||||
use Espo\Core\Field\Currency;
|
||||
|
||||
use stdClass;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @implements AttributeExtractor<Currency>
|
||||
*/
|
||||
class CurrencyAttributeExtractor implements AttributeExtractor
|
||||
{
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private Defs $ormDefs
|
||||
) {}
|
||||
|
||||
public function extract(object $value, string $field): stdClass
|
||||
{
|
||||
if (!$value instanceof Currency) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
$useString = $this->ormDefs
|
||||
->getEntity($this->entityType)
|
||||
->getField($field)
|
||||
->getType() === Entity::VARCHAR;
|
||||
|
||||
$amount = $useString ?
|
||||
$value->getAmountAsString() :
|
||||
$value->getAmount();
|
||||
|
||||
return (object) [
|
||||
$field => $amount,
|
||||
$field . 'Currency' => $value->getCode(),
|
||||
];
|
||||
}
|
||||
|
||||
public function extractFromNull(string $field): stdClass
|
||||
{
|
||||
return (object) [
|
||||
$field => null,
|
||||
$field . 'Currency' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
55
application/Espo/Core/Field/Currency/CurrencyFactory.php
Normal file
55
application/Espo/Core/Field/Currency/CurrencyFactory.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\Currency;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\Value\ValueFactory;
|
||||
use Espo\Core\Field\Currency;
|
||||
use RuntimeException;
|
||||
|
||||
class CurrencyFactory implements ValueFactory
|
||||
{
|
||||
public function isCreatableFromEntity(Entity $entity, string $field): bool
|
||||
{
|
||||
return $entity->get($field) !== null && $entity->get($field . 'Currency') !== null;
|
||||
}
|
||||
|
||||
public function createFromEntity(Entity $entity, string $field): Currency
|
||||
{
|
||||
if (!$this->isCreatableFromEntity($entity, $field)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
return new Currency(
|
||||
$entity->get($field),
|
||||
$entity->get($field . 'Currency')
|
||||
);
|
||||
}
|
||||
}
|
||||
304
application/Espo/Core/Field/Date.php
Normal file
304
application/Espo/Core/Field/Date.php
Normal file
@@ -0,0 +1,304 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field;
|
||||
|
||||
use Espo\Core\Field\DateTime\DateTimeable;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use DateInterval;
|
||||
use DateTimeZone;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* A date value object. Immutable.
|
||||
*/
|
||||
class Date implements DateTimeable
|
||||
{
|
||||
private string $value;
|
||||
private DateTimeImmutable $dateTime;
|
||||
|
||||
private const SYSTEM_FORMAT = 'Y-m-d';
|
||||
|
||||
public function __construct(string $value)
|
||||
{
|
||||
if (!$value) {
|
||||
throw new RuntimeException("Empty value.");
|
||||
}
|
||||
|
||||
$this->value = $value;
|
||||
|
||||
$parsedValue = DateTimeImmutable::createFromFormat(
|
||||
'!' . self::SYSTEM_FORMAT,
|
||||
$value,
|
||||
new DateTimeZone('UTC')
|
||||
);
|
||||
|
||||
if ($parsedValue === false) {
|
||||
throw new RuntimeException("Bad value.");
|
||||
}
|
||||
|
||||
$this->dateTime = $parsedValue;
|
||||
|
||||
if ($this->value !== $this->dateTime->format(self::SYSTEM_FORMAT)) {
|
||||
throw new RuntimeException("Bad value.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string value in `Y-m-d` format.
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DateTimeImmutable.
|
||||
*/
|
||||
public function toDateTime(): DateTimeImmutable
|
||||
{
|
||||
return $this->dateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a timestamp.
|
||||
*/
|
||||
public function toTimestamp(): int
|
||||
{
|
||||
return $this->dateTime->getTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a year.
|
||||
*/
|
||||
public function getYear(): int
|
||||
{
|
||||
return (int) $this->dateTime->format('Y');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a month.
|
||||
*/
|
||||
public function getMonth(): int
|
||||
{
|
||||
return (int) $this->dateTime->format('n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a day (of month).
|
||||
*/
|
||||
public function getDay(): int
|
||||
{
|
||||
return (int) $this->dateTime->format('j');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a day of week. 0 (for Sunday) through 6 (for Saturday).
|
||||
*/
|
||||
public function getDayOfWeek(): int
|
||||
{
|
||||
return (int) $this->dateTime->format('w');
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones and modifies.
|
||||
*/
|
||||
public function modify(string $modifier): self
|
||||
{
|
||||
/** @var DateTimeImmutable|false $dateTime */
|
||||
$dateTime = $this->dateTime->modify($modifier);
|
||||
|
||||
if (!$dateTime) {
|
||||
throw new RuntimeException("Modify failure.");
|
||||
}
|
||||
|
||||
return self::fromDateTime($dateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones and adds an interval.
|
||||
*/
|
||||
public function add(DateInterval $interval): self
|
||||
{
|
||||
$dateTime = $this->dateTime->add($interval);
|
||||
|
||||
return self::fromDateTime($dateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones and subtracts an interval.
|
||||
*/
|
||||
public function subtract(DateInterval $interval): self
|
||||
{
|
||||
$dateTime = $this->dateTime->sub($interval);
|
||||
|
||||
return self::fromDateTime($dateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add days.
|
||||
*/
|
||||
public function addDays(int $days): self
|
||||
{
|
||||
$modifier = ($days >= 0 ? '+' : '-') . abs($days) . ' days';
|
||||
|
||||
return $this->modify($modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add months.
|
||||
*/
|
||||
public function addMonths(int $months): self
|
||||
{
|
||||
$modifier = ($months >= 0 ? '+' : '-') . abs($months) . ' months';
|
||||
|
||||
return $this->modify($modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add years.
|
||||
*/
|
||||
public function addYears(int $years): self
|
||||
{
|
||||
$modifier = ($years >= 0 ? '+' : '-') . abs($years) . ' years';
|
||||
|
||||
return $this->modify($modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* A difference between another object (date or date-time) and self.
|
||||
*/
|
||||
public function diff(DateTimeable $other): DateInterval
|
||||
{
|
||||
return $this->toDateTime()->diff($other->toDateTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether greater than a given value.
|
||||
*/
|
||||
public function isGreaterThan(DateTimeable $other): bool
|
||||
{
|
||||
return $this->toDateTime() > $other->toDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether less than a given value.
|
||||
*/
|
||||
public function isLessThan(DateTimeable $other): bool
|
||||
{
|
||||
return $this->toDateTime() < $other->toDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether equals to a given value.
|
||||
*/
|
||||
public function isEqualTo(DateTimeable $other): bool
|
||||
{
|
||||
return $this->toDateTime() == $other->toDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether less than or equals to a given value.
|
||||
* @since 9.0.0
|
||||
*/
|
||||
public function isLessThanOrEqualTo(DateTimeable $other): bool
|
||||
{
|
||||
return $this->isLessThan($other) || $this->isEqualTo($other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether greater than or equals to a given value.
|
||||
* @since 9.0.0
|
||||
*/
|
||||
public function isGreaterThanOrEqualTo(DateTimeable $other): bool
|
||||
{
|
||||
return $this->isGreaterThan($other) || $this->isEqualTo($other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a today.
|
||||
*/
|
||||
public static function createToday(?DateTimeZone $timezone = null): self
|
||||
{
|
||||
$now = new DateTimeImmutable();
|
||||
|
||||
if ($timezone) {
|
||||
$now = $now->setTimezone($timezone);
|
||||
}
|
||||
|
||||
return self::fromDateTime($now);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from a string with a date in `Y-m-d` format.
|
||||
*/
|
||||
public static function fromString(string $value): self
|
||||
{
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from a DateTimeInterface.
|
||||
*/
|
||||
public static function fromDateTime(DateTimeInterface $dateTime): self
|
||||
{
|
||||
$value = $dateTime->format(self::SYSTEM_FORMAT);
|
||||
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of v8.1. Use `toString` instead.
|
||||
* @todo Remove in v10.0.
|
||||
*/
|
||||
public function getString(): string
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of v8.1. Use `toDateTime` instead.
|
||||
* @todo Remove in v10.0.
|
||||
*/
|
||||
public function getDateTime(): DateTimeImmutable
|
||||
{
|
||||
return $this->toDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of v8.1. Use `toTimestamp` instead.
|
||||
* @todo Remove in v10.0.
|
||||
*/
|
||||
public function getTimestamp(): int
|
||||
{
|
||||
return $this->toTimestamp();
|
||||
}
|
||||
}
|
||||
64
application/Espo/Core/Field/Date/DateAttributeExtractor.php
Normal file
64
application/Espo/Core/Field/Date/DateAttributeExtractor.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\Date;
|
||||
|
||||
use Espo\ORM\Value\AttributeExtractor;
|
||||
|
||||
use Espo\Core\Field\Date;
|
||||
|
||||
use stdClass;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @implements AttributeExtractor<Date>
|
||||
*/
|
||||
class DateAttributeExtractor implements AttributeExtractor
|
||||
{
|
||||
/**
|
||||
* @param Date $value
|
||||
*/
|
||||
public function extract(object $value, string $field): stdClass
|
||||
{
|
||||
if (!$value instanceof Date) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
return (object) [
|
||||
$field => $value->toString(),
|
||||
];
|
||||
}
|
||||
|
||||
public function extractFromNull(string $field): stdClass
|
||||
{
|
||||
return (object) [
|
||||
$field => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
56
application/Espo/Core/Field/Date/DateFactory.php
Normal file
56
application/Espo/Core/Field/Date/DateFactory.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\Date;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\Value\ValueFactory;
|
||||
|
||||
use Espo\Core\Field\Date;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class DateFactory implements ValueFactory
|
||||
{
|
||||
public function isCreatableFromEntity(Entity $entity, string $field): bool
|
||||
{
|
||||
return $entity->get($field) !== null;
|
||||
}
|
||||
|
||||
public function createFromEntity(Entity $entity, string $field): Date
|
||||
{
|
||||
if (!$this->isCreatableFromEntity($entity, $field)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
return new Date(
|
||||
$entity->get($field)
|
||||
);
|
||||
}
|
||||
}
|
||||
411
application/Espo/Core/Field/DateTime.php
Normal file
411
application/Espo/Core/Field/DateTime.php
Normal file
@@ -0,0 +1,411 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field;
|
||||
|
||||
use Espo\Core\Field\DateTime\DateTimeable;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use DateInterval;
|
||||
use DateTimeZone;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* A date-time value object. Immutable.
|
||||
*/
|
||||
class DateTime implements DateTimeable
|
||||
{
|
||||
private string $value;
|
||||
private DateTimeImmutable $dateTime;
|
||||
|
||||
private const SYSTEM_FORMAT = 'Y-m-d H:i:s';
|
||||
|
||||
public function __construct(string $value)
|
||||
{
|
||||
if (!$value) {
|
||||
throw new RuntimeException("Empty value.");
|
||||
}
|
||||
|
||||
$normValue = strlen($value) === 16 ? $value . ':00' : $value;
|
||||
|
||||
$this->value = $normValue;
|
||||
|
||||
$parsedValue = DateTimeImmutable::createFromFormat(
|
||||
self::SYSTEM_FORMAT,
|
||||
$normValue,
|
||||
new DateTimeZone('UTC')
|
||||
);
|
||||
|
||||
if ($parsedValue === false) {
|
||||
throw new RuntimeException("Bad value.");
|
||||
}
|
||||
|
||||
$this->dateTime = $parsedValue;
|
||||
|
||||
if ($this->value !== $this->dateTime->format(self::SYSTEM_FORMAT)) {
|
||||
throw new RuntimeException("Bad value.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string value in `Y-m-d H:i:s` format.
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DateTimeImmutable.
|
||||
*/
|
||||
public function toDateTime(): DateTimeImmutable
|
||||
{
|
||||
return $this->dateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a timestamp.
|
||||
*/
|
||||
public function toTimestamp(): int
|
||||
{
|
||||
return $this->dateTime->getTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a year.
|
||||
*/
|
||||
public function getYear(): int
|
||||
{
|
||||
return (int) $this->dateTime->format('Y');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a month.
|
||||
*/
|
||||
public function getMonth(): int
|
||||
{
|
||||
return (int) $this->dateTime->format('n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a day (of month).
|
||||
*/
|
||||
public function getDay(): int
|
||||
{
|
||||
return (int) $this->dateTime->format('j');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a day of week. 0 (for Sunday) through 6 (for Saturday).
|
||||
*/
|
||||
public function getDayOfWeek(): int
|
||||
{
|
||||
return (int) $this->dateTime->format('w');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a hour.
|
||||
*/
|
||||
public function getHour(): int
|
||||
{
|
||||
return (int) $this->dateTime->format('G');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a minute.
|
||||
*/
|
||||
public function getMinute(): int
|
||||
{
|
||||
return (int) $this->dateTime->format('i');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a second.
|
||||
*/
|
||||
public function getSecond(): int
|
||||
{
|
||||
return (int) $this->dateTime->format('s');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a timezone.
|
||||
*/
|
||||
public function getTimezone(): DateTimeZone
|
||||
{
|
||||
return $this->dateTime->getTimezone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones and modifies.
|
||||
*/
|
||||
public function modify(string $modifier): self
|
||||
{
|
||||
/**
|
||||
* @var DateTimeImmutable|false $dateTime
|
||||
*/
|
||||
$dateTime = $this->dateTime->modify($modifier);
|
||||
|
||||
if (!$dateTime) {
|
||||
throw new RuntimeException("Modify failure.");
|
||||
}
|
||||
|
||||
return self::fromDateTime($dateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones and adds an interval.
|
||||
*/
|
||||
public function add(DateInterval $interval): self
|
||||
{
|
||||
$dateTime = $this->dateTime->add($interval);
|
||||
|
||||
return self::fromDateTime($dateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones and subtracts an interval.
|
||||
*/
|
||||
public function subtract(DateInterval $interval): self
|
||||
{
|
||||
$dateTime = $this->dateTime->sub($interval);
|
||||
|
||||
return self::fromDateTime($dateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add days.
|
||||
*/
|
||||
public function addDays(int $days): self
|
||||
{
|
||||
$modifier = ($days >= 0 ? '+' : '-') . abs($days) . ' days';
|
||||
|
||||
return $this->modify($modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add months.
|
||||
*/
|
||||
public function addMonths(int $months): self
|
||||
{
|
||||
$modifier = ($months >= 0 ? '+' : '-') . abs($months) . ' months';
|
||||
|
||||
return $this->modify($modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add years.
|
||||
*/
|
||||
public function addYears(int $years): self
|
||||
{
|
||||
$modifier = ($years >= 0 ? '+' : '-') . abs($years) . ' years';
|
||||
|
||||
return $this->modify($modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add hours.
|
||||
*/
|
||||
public function addHours(int $hours): self
|
||||
{
|
||||
$modifier = ($hours >= 0 ? '+' : '-') . abs($hours) . ' hours';
|
||||
|
||||
return $this->modify($modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add minutes.
|
||||
*/
|
||||
public function addMinutes(int $minutes): self
|
||||
{
|
||||
$modifier = ($minutes >= 0 ? '+' : '-') . abs($minutes) . ' minutes';
|
||||
|
||||
return $this->modify($modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add seconds.
|
||||
*/
|
||||
public function addSeconds(int $seconds): self
|
||||
{
|
||||
$modifier = ($seconds >= 0 ? '+' : '-') . abs($seconds) . ' seconds';
|
||||
|
||||
return $this->modify($modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* A difference between another object (date or date-time) and self.
|
||||
*/
|
||||
public function diff(DateTimeable $other): DateInterval
|
||||
{
|
||||
return $this->toDateTime()->diff($other->toDateTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones and apply a timezone.
|
||||
*/
|
||||
public function withTimezone(DateTimeZone $timezone): self
|
||||
{
|
||||
$dateTime = $this->dateTime->setTimezone($timezone);
|
||||
|
||||
return self::fromDateTime($dateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones and sets time. Null preserves a current value.
|
||||
*/
|
||||
public function withTime(?int $hour, ?int $minute, ?int $second = 0): self
|
||||
{
|
||||
$dateTime = $this->dateTime->setTime(
|
||||
$hour ?? $this->getHour(),
|
||||
$minute ?? $this->getMinute(),
|
||||
$second ?? $this->getSecond()
|
||||
);
|
||||
|
||||
return self::fromDateTime($dateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether greater than a given value.
|
||||
*/
|
||||
public function isGreaterThan(DateTimeable $other): bool
|
||||
{
|
||||
return $this->toDateTime() > $other->toDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether less than a given value.
|
||||
*/
|
||||
public function isLessThan(DateTimeable $other): bool
|
||||
{
|
||||
return $this->toDateTime() < $other->toDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether equals to a given value.
|
||||
*/
|
||||
public function isEqualTo(DateTimeable $other): bool
|
||||
{
|
||||
return $this->toDateTime() == $other->toDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether less than or equals to a given value.
|
||||
* @since 9.0.0
|
||||
*/
|
||||
public function isLessThanOrEqualTo(DateTimeable $other): bool
|
||||
{
|
||||
return $this->isLessThan($other) || $this->isEqualTo($other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether greater than or equals to a given value.
|
||||
* @since 9.0.0
|
||||
*/
|
||||
public function isGreaterThanOrEqualTo(DateTimeable $other): bool
|
||||
{
|
||||
return $this->isGreaterThan($other) || $this->isEqualTo($other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a current time.
|
||||
*/
|
||||
public static function createNow(): self
|
||||
{
|
||||
return self::fromDateTime(new DateTimeImmutable());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from a string with a date-time in `Y-m-d H:i:s` format.
|
||||
*/
|
||||
public static function fromString(string $value): self
|
||||
{
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from a timestamp.
|
||||
*/
|
||||
public static function fromTimestamp(int $timestamp): self
|
||||
{
|
||||
$dateTime = (new DateTimeImmutable)->setTimestamp($timestamp);
|
||||
|
||||
return self::fromDateTime($dateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from a DateTimeInterface.
|
||||
*/
|
||||
public static function fromDateTime(DateTimeInterface $dateTime): self
|
||||
{
|
||||
/** @var DateTimeImmutable $value */
|
||||
$value = DateTimeImmutable::createFromFormat(
|
||||
self::SYSTEM_FORMAT,
|
||||
$dateTime->format(self::SYSTEM_FORMAT),
|
||||
$dateTime->getTimezone()
|
||||
);
|
||||
|
||||
$utcValue = $value
|
||||
->setTimezone(new DateTimeZone('UTC'))
|
||||
->format(self::SYSTEM_FORMAT);
|
||||
|
||||
$obj = new self($utcValue);
|
||||
|
||||
$obj->dateTime = $obj->dateTime->setTimezone($dateTime->getTimezone());
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of v8.1. Use `toString` instead.
|
||||
* @todo Remove in v10.0.
|
||||
*/
|
||||
public function getString(): string
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of v8.1. Use `toDateTime` instead.
|
||||
* @todo Remove in v10.0.
|
||||
*/
|
||||
public function getDateTime(): DateTimeImmutable
|
||||
{
|
||||
return $this->toDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of v8.1. Use `toTimestamp` instead.
|
||||
* @todo Remove in v10.0.
|
||||
*/
|
||||
public function getTimestamp(): int
|
||||
{
|
||||
return $this->toTimestamp();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\DateTime;
|
||||
|
||||
use Espo\ORM\Value\AttributeExtractor;
|
||||
|
||||
use Espo\Core\Field\DateTime;
|
||||
|
||||
use stdClass;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @implements AttributeExtractor<DateTime>
|
||||
*/
|
||||
class DateTimeAttributeExtractor implements AttributeExtractor
|
||||
{
|
||||
/**
|
||||
* @param DateTime $value
|
||||
*/
|
||||
public function extract(object $value, string $field): stdClass
|
||||
{
|
||||
if (!$value instanceof DateTime) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
return (object) [
|
||||
$field => $value->toString(),
|
||||
];
|
||||
}
|
||||
|
||||
public function extractFromNull(string $field): stdClass
|
||||
{
|
||||
return (object) [
|
||||
$field => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
55
application/Espo/Core/Field/DateTime/DateTimeFactory.php
Normal file
55
application/Espo/Core/Field/DateTime/DateTimeFactory.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\DateTime;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\Value\ValueFactory;
|
||||
use Espo\Core\Field\DateTime;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class DateTimeFactory implements ValueFactory
|
||||
{
|
||||
public function isCreatableFromEntity(Entity $entity, string $field): bool
|
||||
{
|
||||
return $entity->get($field) !== null;
|
||||
}
|
||||
|
||||
public function createFromEntity(Entity $entity, string $field): DateTime
|
||||
{
|
||||
if (!$this->isCreatableFromEntity($entity, $field)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
return new DateTime(
|
||||
$entity->get($field)
|
||||
);
|
||||
}
|
||||
}
|
||||
37
application/Espo/Core/Field/DateTime/DateTimeable.php
Normal file
37
application/Espo/Core/Field/DateTime/DateTimeable.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\DateTime;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
interface DateTimeable
|
||||
{
|
||||
public function toDateTime(): DateTimeImmutable;
|
||||
}
|
||||
528
application/Espo/Core/Field/DateTimeOptional.php
Normal file
528
application/Espo/Core/Field/DateTimeOptional.php
Normal file
@@ -0,0 +1,528 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field;
|
||||
|
||||
use Espo\Core\Field\DateTime\DateTimeable;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use DateInterval;
|
||||
use DateTimeZone;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* A date-time or date. Immutable.
|
||||
*/
|
||||
class DateTimeOptional implements DateTimeable
|
||||
{
|
||||
private ?DateTime $dateTimeValue = null;
|
||||
private ?Date $dateValue = null;
|
||||
|
||||
private const SYSTEM_FORMAT = 'Y-m-d H:i:s';
|
||||
private const SYSTEM_FORMAT_DATE = 'Y-m-d';
|
||||
|
||||
public function __construct(string $value)
|
||||
{
|
||||
if (self::isStringDateTime($value)) {
|
||||
$this->dateTimeValue = new DateTime($value);
|
||||
} else {
|
||||
$this->dateValue = new Date($value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from a string with a date-time in `Y-m-d H:i:s` format or date in `Y-m-d`.
|
||||
*/
|
||||
public static function fromString(string $value): self
|
||||
{
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from a string with a date-time in `Y-m-d H:i:s` format.
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public static function fromDateTimeString(string $value): self
|
||||
{
|
||||
if (!self::isStringDateTime($value)) {
|
||||
throw new RuntimeException("Bad value.");
|
||||
}
|
||||
|
||||
return self::fromString($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string value in `Y-m-d H:i:s` format.
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
return $this->getActualValue()->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DateTimeImmutable.
|
||||
*/
|
||||
public function toDateTime(): DateTimeImmutable
|
||||
{
|
||||
return $this->getActualValue()->toDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a timestamp.
|
||||
*/
|
||||
public function toTimestamp(): int
|
||||
{
|
||||
return $this->getActualValue()->toDateTime()->getTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a year.
|
||||
*/
|
||||
public function getYear(): int
|
||||
{
|
||||
return $this->getActualValue()->getYear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a month.
|
||||
*/
|
||||
public function getMonth(): int
|
||||
{
|
||||
return $this->getActualValue()->getMonth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a day (of month).
|
||||
*/
|
||||
public function getDay(): int
|
||||
{
|
||||
return $this->getActualValue()->getDay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a day of week. 0 (for Sunday) through 6 (for Saturday).
|
||||
*/
|
||||
public function getDayOfWeek(): int
|
||||
{
|
||||
return $this->getActualValue()->getDayOfWeek();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an hour.
|
||||
*/
|
||||
public function getHour(): int
|
||||
{
|
||||
if ($this->isAllDay()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @var DateTime $value */
|
||||
$value = $this->getActualValue();
|
||||
|
||||
return $value->getHour();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a minute.
|
||||
*/
|
||||
public function getMinute(): int
|
||||
{
|
||||
if ($this->isAllDay()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @var DateTime $value */
|
||||
$value = $this->getActualValue();
|
||||
|
||||
return $value->getMinute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a second.
|
||||
*/
|
||||
public function getSecond(): int
|
||||
{
|
||||
if ($this->isAllDay()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @var DateTime $value */
|
||||
$value = $this->getActualValue();
|
||||
|
||||
return $value->getSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether is all-day (no time part).
|
||||
*/
|
||||
public function isAllDay(): bool
|
||||
{
|
||||
return $this->dateValue !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a timezone.
|
||||
*/
|
||||
public function getTimezone(): DateTimeZone
|
||||
{
|
||||
return $this->toDateTime()->getTimezone();
|
||||
}
|
||||
|
||||
private function getActualValue(): Date|DateTime
|
||||
{
|
||||
/** @var Date|DateTime */
|
||||
return $this->dateValue ?? $this->dateTimeValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones and apply a timezone. Non-all-day value is created.
|
||||
*/
|
||||
public function withTimezone(DateTimeZone $timezone): self
|
||||
{
|
||||
if ($this->isAllDay()) {
|
||||
$dateTime = $this->getActualValue()->toDateTime()->setTimezone($timezone);
|
||||
|
||||
return self::fromDateTime($dateTime);
|
||||
}
|
||||
|
||||
/** @var DateTime $value */
|
||||
$value = $this->getActualValue();
|
||||
|
||||
$dateTime = $value->withTimezone($timezone)->toDateTime();
|
||||
|
||||
return self::fromDateTime($dateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones and sets time. Null preserves a current value.
|
||||
*/
|
||||
public function withTime(?int $hour, ?int $minute, ?int $second = 0): self
|
||||
{
|
||||
if ($this->isAllDay()) {
|
||||
$dateTime = DateTime::fromDateTime($this->getActualValue()->toDateTime())
|
||||
->withTime($hour, $minute, $second);
|
||||
|
||||
return self::fromDateTime($dateTime->toDateTime());
|
||||
}
|
||||
|
||||
/** @var DateTime $value */
|
||||
$value = $this->getActualValue();
|
||||
|
||||
$dateTime = $value->withTime($hour, $minute, $second);
|
||||
|
||||
return self::fromDateTime($dateTime->toDateTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones and modifies.
|
||||
*/
|
||||
public function modify(string $modifier): self
|
||||
{
|
||||
if ($this->isAllDay()) {
|
||||
assert($this->dateValue !== null);
|
||||
|
||||
return self::fromDateTimeAllDay(
|
||||
$this->dateValue->modify($modifier)->toDateTime()
|
||||
);
|
||||
}
|
||||
|
||||
assert($this->dateTimeValue !== null);
|
||||
|
||||
return self::fromDateTime(
|
||||
$this->dateTimeValue->modify($modifier)->toDateTime()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones and adds an interval.
|
||||
*/
|
||||
public function add(DateInterval $interval): self
|
||||
{
|
||||
if ($this->isAllDay()) {
|
||||
assert($this->dateValue !== null);
|
||||
|
||||
return self::fromDateTimeAllDay(
|
||||
$this->dateValue->add($interval)->toDateTime()
|
||||
);
|
||||
}
|
||||
|
||||
assert($this->dateTimeValue !== null);
|
||||
|
||||
return self::fromDateTime(
|
||||
$this->dateTimeValue->add($interval)->toDateTime()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones and subtracts an interval.
|
||||
*/
|
||||
public function subtract(DateInterval $interval): self
|
||||
{
|
||||
if ($this->isAllDay()) {
|
||||
assert($this->dateValue !== null);
|
||||
|
||||
return self::fromDateTimeAllDay(
|
||||
$this->dateValue->subtract($interval)->toDateTime()
|
||||
);
|
||||
}
|
||||
|
||||
assert($this->dateTimeValue !== null);
|
||||
|
||||
return self::fromDateTime(
|
||||
$this->dateTimeValue->subtract($interval)->toDateTime()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add days.
|
||||
*/
|
||||
public function addDays(int $days): self
|
||||
{
|
||||
$modifier = ($days >= 0 ? '+' : '-') . abs($days) . ' days';
|
||||
|
||||
return $this->modify($modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add months.
|
||||
*/
|
||||
public function addMonths(int $months): self
|
||||
{
|
||||
$modifier = ($months >= 0 ? '+' : '-') . abs($months) . ' months';
|
||||
|
||||
return $this->modify($modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add years.
|
||||
*/
|
||||
public function addYears(int $years): self
|
||||
{
|
||||
$modifier = ($years >= 0 ? '+' : '-') . abs($years) . ' years';
|
||||
|
||||
return $this->modify($modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add hours.
|
||||
*/
|
||||
public function addHours(int $hours): self
|
||||
{
|
||||
$modifier = ($hours >= 0 ? '+' : '-') . abs($hours) . ' hours';
|
||||
|
||||
return $this->modify($modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add minutes.
|
||||
*/
|
||||
public function addMinutes(int $minutes): self
|
||||
{
|
||||
$modifier = ($minutes >= 0 ? '+' : '-') . abs($minutes) . ' minutes';
|
||||
|
||||
return $this->modify($modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add seconds.
|
||||
*/
|
||||
public function addSeconds(int $seconds): self
|
||||
{
|
||||
$modifier = ($seconds >= 0 ? '+' : '-') . abs($seconds) . ' seconds';
|
||||
|
||||
return $this->modify($modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* A difference between another object (date or date-time) and self.
|
||||
*/
|
||||
public function diff(DateTimeable $other): DateInterval
|
||||
{
|
||||
return $this->toDateTime()->diff($other->toDateTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether greater than a given value.
|
||||
*/
|
||||
public function isGreaterThan(DateTimeable $other): bool
|
||||
{
|
||||
return $this->toDateTime() > $other->toDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether less than a given value.
|
||||
*/
|
||||
public function isLessThan(DateTimeable $other): bool
|
||||
{
|
||||
return $this->toDateTime() < $other->toDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether equals to a given value.
|
||||
*/
|
||||
public function isEqualTo(DateTimeable $other): bool
|
||||
{
|
||||
return $this->toDateTime() == $other->toDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether less than or equals to a given value.
|
||||
* @since 9.0.0
|
||||
*/
|
||||
public function isLessThanOrEqualTo(DateTimeable $other): bool
|
||||
{
|
||||
return $this->isLessThan($other) || $this->isEqualTo($other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether greater than or equals to a given value.
|
||||
* @since 9.0.0
|
||||
*/
|
||||
public function isGreaterThanOrEqualTo(DateTimeable $other): bool
|
||||
{
|
||||
return $this->isGreaterThan($other) || $this->isEqualTo($other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a current time.
|
||||
*/
|
||||
public static function createNow(): self
|
||||
{
|
||||
return self::fromDateTime(new DateTimeImmutable());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a today.
|
||||
*/
|
||||
public static function createToday(?DateTimeZone $timezone = null): self
|
||||
{
|
||||
$now = new DateTimeImmutable();
|
||||
|
||||
if ($timezone) {
|
||||
$now = $now->setTimezone($timezone);
|
||||
}
|
||||
|
||||
return self::fromDateTimeAllDay($now);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from a string with a date in `Y-m-d` format.
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public static function fromDateString(string $value): self
|
||||
{
|
||||
if (self::isStringDateTime($value)) {
|
||||
throw new RuntimeException("Bad value.");
|
||||
}
|
||||
|
||||
return self::fromString($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from a timestamp.
|
||||
*/
|
||||
public static function fromTimestamp(int $timestamp): self
|
||||
{
|
||||
$dateTime = (new DateTimeImmutable)->setTimestamp($timestamp);
|
||||
|
||||
return self::fromDateTime($dateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from a DateTimeInterface.
|
||||
*/
|
||||
public static function fromDateTime(DateTimeInterface $dateTime): self
|
||||
{
|
||||
/** @var DateTimeImmutable $value */
|
||||
$value = DateTimeImmutable::createFromFormat(
|
||||
self::SYSTEM_FORMAT,
|
||||
$dateTime->format(self::SYSTEM_FORMAT),
|
||||
$dateTime->getTimezone()
|
||||
);
|
||||
|
||||
$utcValue = $value
|
||||
->setTimezone(new DateTimeZone('UTC'))
|
||||
->format(self::SYSTEM_FORMAT);
|
||||
|
||||
$obj = self::fromString($utcValue);
|
||||
|
||||
assert($obj->dateTimeValue !== null);
|
||||
|
||||
$obj->dateTimeValue = $obj->dateTimeValue->withTimezone($dateTime->getTimezone());
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create all-day from a DateTimeInterface.
|
||||
*/
|
||||
public static function fromDateTimeAllDay(DateTimeInterface $dateTime): self
|
||||
{
|
||||
$value = $dateTime->format(self::SYSTEM_FORMAT_DATE);
|
||||
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
private static function isStringDateTime(string $value): bool
|
||||
{
|
||||
if (strlen($value) > 10) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of v8.1. Use `toString` instead.
|
||||
* @todo Remove in v10.0.
|
||||
*/
|
||||
public function getString(): string
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of v8.1. Use `toDateTime` instead.
|
||||
* @todo Remove in v10.0.
|
||||
*/
|
||||
public function getDateTime(): DateTimeImmutable
|
||||
{
|
||||
return $this->toDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of v8.1. Use `toTimestamp` instead.
|
||||
* @todo Remove in v10.0.
|
||||
*/
|
||||
public function getTimestamp(): int
|
||||
{
|
||||
return $this->toTimestamp();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\DateTimeOptional;
|
||||
|
||||
use Espo\ORM\Value\AttributeExtractor;
|
||||
|
||||
use Espo\Core\Field\DateTimeOptional;
|
||||
|
||||
use stdClass;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @implements AttributeExtractor<DateTimeOptional>
|
||||
*/
|
||||
class DateTimeOptionalAttributeExtractor implements AttributeExtractor
|
||||
{
|
||||
/**
|
||||
* @param DateTimeOptional $value
|
||||
*/
|
||||
public function extract(object $value, string $field): stdClass
|
||||
{
|
||||
if (!$value instanceof DateTimeOptional) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
if ($value->isAllDay()) {
|
||||
return (object) [
|
||||
$field . 'Date' => $value->toString(),
|
||||
$field => null,
|
||||
];
|
||||
}
|
||||
|
||||
return (object) [
|
||||
$field => $value->toString(),
|
||||
$field . 'Date' => null,
|
||||
];
|
||||
}
|
||||
|
||||
public function extractFromNull(string $field): stdClass
|
||||
{
|
||||
return (object) [
|
||||
$field => null,
|
||||
$field . 'Date' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\DateTimeOptional;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\Value\ValueFactory;
|
||||
use Espo\Core\Field\DateTimeOptional;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class DateTimeOptionalFactory implements ValueFactory
|
||||
{
|
||||
public function isCreatableFromEntity(Entity $entity, string $field): bool
|
||||
{
|
||||
return $entity->get($field) !== null || $entity->get($field . 'Date') !== null;
|
||||
}
|
||||
|
||||
public function createFromEntity(Entity $entity, string $field): DateTimeOptional
|
||||
{
|
||||
if (!$this->isCreatableFromEntity($entity, $field)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$stringValue = $entity->get($field . 'Date') ?? $entity->get($field);
|
||||
|
||||
return new DateTimeOptional($stringValue);
|
||||
}
|
||||
}
|
||||
147
application/Espo/Core/Field/EmailAddress.php
Normal file
147
application/Espo/Core/Field/EmailAddress.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
use FILTER_VALIDATE_EMAIL;
|
||||
|
||||
/**
|
||||
* An email address value. Immutable.
|
||||
*/
|
||||
class EmailAddress
|
||||
{
|
||||
private string $address;
|
||||
private bool $isOptedOut = false;
|
||||
private bool $isInvalid = false;
|
||||
|
||||
public function __construct(string $address)
|
||||
{
|
||||
if ($address === '') {
|
||||
throw new RuntimeException("Empty email address.");
|
||||
}
|
||||
|
||||
if (!filter_var($address, FILTER_VALIDATE_EMAIL)) {
|
||||
throw new RuntimeException("Not valid email address '{$address}'.");
|
||||
}
|
||||
|
||||
$this->address = $address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an address.
|
||||
*/
|
||||
public function getAddress(): string
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether opted-out.
|
||||
*/
|
||||
public function isOptedOut(): bool
|
||||
{
|
||||
return $this->isOptedOut;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether invalid.
|
||||
*/
|
||||
public function isInvalid(): bool
|
||||
{
|
||||
return $this->isInvalid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone set invalid.
|
||||
*/
|
||||
public function invalid(): self
|
||||
{
|
||||
$obj = $this->clone();
|
||||
|
||||
$obj->isInvalid = true;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone set not invalid.
|
||||
*/
|
||||
public function notInvalid(): self
|
||||
{
|
||||
$obj = $this->clone();
|
||||
|
||||
$obj->isInvalid = false;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone set opted-out.
|
||||
*/
|
||||
public function optedOut(): self
|
||||
{
|
||||
$obj = $this->clone();
|
||||
|
||||
$obj->isOptedOut = true;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone set not opted-out.
|
||||
*/
|
||||
public function notOptedOut(): self
|
||||
{
|
||||
$obj = $this->clone();
|
||||
|
||||
$obj->isOptedOut = false;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from an address.
|
||||
*/
|
||||
public static function create(string $address): self
|
||||
{
|
||||
return new self($address);
|
||||
}
|
||||
|
||||
private function clone(): self
|
||||
{
|
||||
$obj = new self($this->address);
|
||||
|
||||
$obj->isInvalid = $this->isInvalid;
|
||||
$obj->isOptedOut = $this->isOptedOut;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\EmailAddress;
|
||||
|
||||
use Espo\ORM\Value\AttributeExtractor;
|
||||
|
||||
use Espo\Core\Field\EmailAddressGroup;
|
||||
|
||||
use stdClass;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @implements AttributeExtractor<EmailAddressGroup>
|
||||
*/
|
||||
class EmailAddressGroupAttributeExtractor implements AttributeExtractor
|
||||
{
|
||||
/**
|
||||
* @param EmailAddressGroup $group
|
||||
*/
|
||||
public function extract(object $group, string $field): stdClass
|
||||
{
|
||||
if (!$group instanceof EmailAddressGroup) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
$primaryAddress = $group->getPrimary() ? $group->getPrimary()->getAddress() : null;
|
||||
|
||||
$dataList = [];
|
||||
|
||||
foreach ($group->getList() as $emailAddress) {
|
||||
$dataList[] = (object) [
|
||||
'emailAddress' => $emailAddress->getAddress(),
|
||||
'lower' => strtolower($emailAddress->getAddress()),
|
||||
'primary' => $primaryAddress && $emailAddress->getAddress() === $primaryAddress,
|
||||
'optOut' => $emailAddress->isOptedOut(),
|
||||
'invalid' => $emailAddress->isInvalid(),
|
||||
];
|
||||
}
|
||||
|
||||
return (object) [
|
||||
$field => $primaryAddress,
|
||||
$field . 'Data' => $dataList,
|
||||
];
|
||||
}
|
||||
|
||||
public function extractFromNull(string $field): stdClass
|
||||
{
|
||||
return (object) [
|
||||
$field => null,
|
||||
$field . 'Data' => [],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\EmailAddress;
|
||||
|
||||
use Espo\Core\ORM\Type\FieldType;
|
||||
use Espo\Entities\EmailAddress as EmailAddressEntity;
|
||||
use Espo\ORM\Defs\Params\FieldParam;
|
||||
use Espo\Repositories\EmailAddress as Repository;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Value\ValueFactory;
|
||||
|
||||
use Espo\Core\Field\EmailAddress;
|
||||
use Espo\Core\Field\EmailAddressGroup;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
|
||||
use RuntimeException;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* An email address group factory.
|
||||
*/
|
||||
class EmailAddressGroupFactory implements ValueFactory
|
||||
{
|
||||
private Metadata $metadata;
|
||||
private EntityManager $entityManager;
|
||||
|
||||
/**
|
||||
* @todo Use OrmDefs instead of Metadata.
|
||||
*/
|
||||
public function __construct(Metadata $metadata, EntityManager $entityManager)
|
||||
{
|
||||
$this->metadata = $metadata;
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
public function isCreatableFromEntity(Entity $entity, string $field): bool
|
||||
{
|
||||
$type = $this->metadata->get(['entityDefs', $entity->getEntityType(), 'fields', $field, FieldParam::TYPE]);
|
||||
|
||||
if ($type !== FieldType::EMAIL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function createFromEntity(Entity $entity, string $field): EmailAddressGroup
|
||||
{
|
||||
if (!$this->isCreatableFromEntity($entity, $field)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$emailAddressList = [];
|
||||
|
||||
$primaryEmailAddress = null;
|
||||
|
||||
$dataList = null;
|
||||
|
||||
$dataAttribute = $field . 'Data';
|
||||
|
||||
if ($entity->has($dataAttribute)) {
|
||||
$dataList = $this->sanitizeDataList(
|
||||
$entity->get($dataAttribute)
|
||||
);
|
||||
}
|
||||
|
||||
if (!$dataList && $entity->has($field) && !$entity->get($field)) {
|
||||
$dataList = [];
|
||||
}
|
||||
|
||||
if (!$dataList) {
|
||||
/** @var Repository $repository */
|
||||
$repository = $this->entityManager->getRepository(EmailAddressEntity::ENTITY_TYPE);
|
||||
|
||||
$dataList = $repository->getEmailAddressData($entity);
|
||||
}
|
||||
|
||||
foreach ($dataList as $item) {
|
||||
$emailAddress = EmailAddress::create($item->emailAddress);
|
||||
|
||||
if ($item->optOut ?? false) {
|
||||
$emailAddress = $emailAddress->optedOut();
|
||||
}
|
||||
|
||||
if ($item->invalid ?? false) {
|
||||
$emailAddress = $emailAddress->invalid();
|
||||
}
|
||||
|
||||
if ($item->primary ?? false) {
|
||||
$primaryEmailAddress = $emailAddress;
|
||||
}
|
||||
|
||||
$emailAddressList[] = $emailAddress;
|
||||
}
|
||||
|
||||
$group = EmailAddressGroup::create($emailAddressList);
|
||||
|
||||
if ($primaryEmailAddress) {
|
||||
$group = $group->withPrimary($primaryEmailAddress);
|
||||
}
|
||||
|
||||
return $group;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>|stdClass> $dataList
|
||||
* @return stdClass[]
|
||||
*/
|
||||
private function sanitizeDataList(array $dataList): array
|
||||
{
|
||||
$sanitizedDataList = [];
|
||||
|
||||
foreach ($dataList as $item) {
|
||||
if (is_array($item)) {
|
||||
$sanitizedDataList[] = (object) $item;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_object($item)) {
|
||||
throw new RuntimeException("Bad data.");
|
||||
}
|
||||
|
||||
$sanitizedDataList[] = $item;
|
||||
}
|
||||
|
||||
return $sanitizedDataList;
|
||||
}
|
||||
}
|
||||
298
application/Espo/Core/Field/EmailAddressGroup.php
Normal file
298
application/Espo/Core/Field/EmailAddressGroup.php
Normal file
@@ -0,0 +1,298 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* An email address group. Contains a list of email addresses. One email address is set as primary.
|
||||
* If not empty, then there always should be a primary address. Immutable.
|
||||
*/
|
||||
class EmailAddressGroup
|
||||
{
|
||||
/** @var EmailAddress[] */
|
||||
private array $list = [];
|
||||
private ?EmailAddress $primary = null;
|
||||
|
||||
/**
|
||||
* @param EmailAddress[] $list
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function __construct(array $list = [])
|
||||
{
|
||||
foreach ($list as $item) {
|
||||
$this->list[] = clone $item;
|
||||
}
|
||||
|
||||
$this->validateList();
|
||||
|
||||
if (count($this->list) !== 0) {
|
||||
$this->primary = $this->list[0];
|
||||
}
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$newList = [];
|
||||
|
||||
foreach ($this->list as $item) {
|
||||
$newList[] = clone $item;
|
||||
}
|
||||
|
||||
$this->list = $newList;
|
||||
|
||||
if ($this->primary) {
|
||||
$this->primary = clone $this->primary;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a primary address as a string. If no primary, then returns null,
|
||||
*/
|
||||
public function getPrimaryAddress(): ?string
|
||||
{
|
||||
$primary = $this->getPrimary();
|
||||
|
||||
if (!$primary) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $primary->getAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a primary email address.
|
||||
*/
|
||||
public function getPrimary(): ?EmailAddress
|
||||
{
|
||||
if ($this->isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->primary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all email addresses.
|
||||
*
|
||||
* @return EmailAddress[]
|
||||
*/
|
||||
public function getList(): array
|
||||
{
|
||||
return $this->list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a number of addresses.
|
||||
*/
|
||||
public function getCount(): int
|
||||
{
|
||||
return count($this->list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of email addresses w/o a primary.
|
||||
*
|
||||
* @return EmailAddress[]
|
||||
*/
|
||||
public function getSecondaryList(): array
|
||||
{
|
||||
$list = [];
|
||||
|
||||
foreach ($this->list as $item) {
|
||||
if ($item === $this->primary) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$list[] = $item;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of email addresses represented as strings.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAddressList(): array
|
||||
{
|
||||
$list = [];
|
||||
|
||||
foreach ($this->list as $item) {
|
||||
$list[] = $item->getAddress();
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an email address by address represented as a string.
|
||||
*/
|
||||
public function getByAddress(string $address): ?EmailAddress
|
||||
{
|
||||
$index = $this->searchAddressInList($address);
|
||||
|
||||
if ($index === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->list[$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an address is in the list.
|
||||
*/
|
||||
public function hasAddress(string $address): bool
|
||||
{
|
||||
return in_array($address, $this->getAddressList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with another primary email address.
|
||||
*/
|
||||
public function withPrimary(EmailAddress $emailAddress): self
|
||||
{
|
||||
$list = $this->list;
|
||||
|
||||
$index = $this->searchAddressInList($emailAddress->getAddress());
|
||||
|
||||
if ($index !== null) {
|
||||
unset($list[$index]);
|
||||
|
||||
$list = array_values($list);
|
||||
}
|
||||
|
||||
$newList = array_merge([$emailAddress], $list);
|
||||
|
||||
return self::create($newList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with an added email address list.
|
||||
*
|
||||
* @param EmailAddress[] $list
|
||||
*/
|
||||
public function withAddedList(array $list): self
|
||||
{
|
||||
$newList = $this->list;
|
||||
|
||||
foreach ($list as $item) {
|
||||
$index = $this->searchAddressInList($item->getAddress());
|
||||
|
||||
if ($index !== null) {
|
||||
$newList[$index] = $item;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$newList[] = $item;
|
||||
}
|
||||
|
||||
return self::create($newList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with an added email address.
|
||||
*/
|
||||
public function withAdded(EmailAddress $emailAddress): self
|
||||
{
|
||||
return $this->withAddedList([$emailAddress]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with removed email address.
|
||||
*/
|
||||
public function withRemoved(EmailAddress $emailAddress): self
|
||||
{
|
||||
return $this->withRemovedByAddress($emailAddress->getAddress());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with removed email address passed by an address.
|
||||
*/
|
||||
public function withRemovedByAddress(string $address): self
|
||||
{
|
||||
$newList = $this->list;
|
||||
|
||||
$index = $this->searchAddressInList($address);
|
||||
|
||||
if ($index !== null) {
|
||||
unset($newList[$index]);
|
||||
|
||||
$newList = array_values($newList);
|
||||
}
|
||||
|
||||
return self::create($newList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create with an optional email address list. A first item will be set as primary.
|
||||
*
|
||||
* @param EmailAddress[] $list
|
||||
*/
|
||||
public static function create(array $list = []): self
|
||||
{
|
||||
return new self($list);
|
||||
}
|
||||
|
||||
private function searchAddressInList(string $address): ?int
|
||||
{
|
||||
foreach ($this->getAddressList() as $i => $item) {
|
||||
if ($item === $address) {
|
||||
return $i;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function validateList(): void
|
||||
{
|
||||
$addressList = [];
|
||||
|
||||
foreach ($this->list as $item) {
|
||||
if (!$item instanceof EmailAddress) {
|
||||
throw new RuntimeException("Bad item.");
|
||||
}
|
||||
|
||||
if (in_array(strtolower($item->getAddress()), $addressList)) {
|
||||
throw new RuntimeException("Address list contains a duplicate.");
|
||||
}
|
||||
|
||||
$addressList[] = strtolower($item->getAddress());
|
||||
}
|
||||
}
|
||||
|
||||
private function isEmpty(): bool
|
||||
{
|
||||
return count($this->list) === 0;
|
||||
}
|
||||
}
|
||||
86
application/Espo/Core/Field/Link.php
Normal file
86
application/Espo/Core/Field/Link.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* A link value object. Immutable.
|
||||
*/
|
||||
class Link
|
||||
{
|
||||
private string $id;
|
||||
private ?string $name = null;
|
||||
|
||||
public function __construct(string $id)
|
||||
{
|
||||
if (!$id) {
|
||||
throw new RuntimeException("Empty ID.");
|
||||
}
|
||||
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an ID.
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a name.
|
||||
*/
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a name.
|
||||
*/
|
||||
public function withName(?string $name): self
|
||||
{
|
||||
$obj = new self($this->id);
|
||||
|
||||
$obj->name = $name;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from an ID.
|
||||
*/
|
||||
public static function create(string $id, ?string $name = null): self
|
||||
{
|
||||
return (new self($id))->withName($name);
|
||||
}
|
||||
}
|
||||
66
application/Espo/Core/Field/Link/LinkAttributeExtractor.php
Normal file
66
application/Espo/Core/Field/Link/LinkAttributeExtractor.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\Link;
|
||||
|
||||
use Espo\ORM\Value\AttributeExtractor;
|
||||
|
||||
use Espo\Core\Field\Link;
|
||||
|
||||
use stdClass;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @implements AttributeExtractor<Link>
|
||||
*/
|
||||
class LinkAttributeExtractor implements AttributeExtractor
|
||||
{
|
||||
/**
|
||||
* @param Link $value
|
||||
*/
|
||||
public function extract(object $value, string $field): stdClass
|
||||
{
|
||||
if (!$value instanceof Link) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
return (object) [
|
||||
$field . 'Id' => $value->getId(),
|
||||
$field . 'Name' => $value->getName(),
|
||||
];
|
||||
}
|
||||
|
||||
public function extractFromNull(string $field): stdClass
|
||||
{
|
||||
return (object) [
|
||||
$field . 'Id' => null,
|
||||
$field . 'Name' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
58
application/Espo/Core/Field/Link/LinkFactory.php
Normal file
58
application/Espo/Core/Field/Link/LinkFactory.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\Link;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\Value\ValueFactory;
|
||||
use Espo\Core\Field\Link;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class LinkFactory implements ValueFactory
|
||||
{
|
||||
public function isCreatableFromEntity(Entity $entity, string $field): bool
|
||||
{
|
||||
return $entity->get($field . 'Id') !== null;
|
||||
}
|
||||
|
||||
public function createFromEntity(Entity $entity, string $field): Link
|
||||
{
|
||||
if (!$this->isCreatableFromEntity($entity, $field)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$id = $entity->get($field . 'Id');
|
||||
$name = $entity->get($field . 'Name');
|
||||
|
||||
return Link
|
||||
::create($id)
|
||||
->withName($name);
|
||||
}
|
||||
}
|
||||
238
application/Espo/Core/Field/LinkMultiple.php
Normal file
238
application/Espo/Core/Field/LinkMultiple.php
Normal file
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* A link-multiple value object. Immutable.
|
||||
*/
|
||||
class LinkMultiple
|
||||
{
|
||||
/**
|
||||
* @param LinkMultipleItem[] $list
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function __construct(private array $list = [])
|
||||
{
|
||||
$this->validateList();
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$newList = [];
|
||||
|
||||
foreach ($this->list as $item) {
|
||||
$newList[] = clone $item;
|
||||
}
|
||||
|
||||
$this->list = $newList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether contains a specific ID.
|
||||
*/
|
||||
public function hasId(string $id): bool
|
||||
{
|
||||
return $this->searchIdInList($id) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of IDs.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getIdList(): array
|
||||
{
|
||||
$idList = [];
|
||||
|
||||
foreach ($this->list as $item) {
|
||||
$idList[] = $item->getId();
|
||||
}
|
||||
|
||||
return $idList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of items.
|
||||
*
|
||||
* @return LinkMultipleItem[]
|
||||
*/
|
||||
public function getList(): array
|
||||
{
|
||||
return $this->list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a number of items.
|
||||
*/
|
||||
public function getCount(): int
|
||||
{
|
||||
return count($this->list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item by ID.
|
||||
*/
|
||||
public function getById(string $id): ?LinkMultipleItem
|
||||
{
|
||||
foreach ($this->list as $item) {
|
||||
if ($item->getId() === $id) {
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with an added ID.
|
||||
*/
|
||||
public function withAddedId(string $id): self
|
||||
{
|
||||
return $this->withAdded(LinkMultipleItem::create($id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with an added IDs.
|
||||
*
|
||||
* @param string[] $idList IDs.
|
||||
*/
|
||||
public function withAddedIdList(array $idList): self
|
||||
{
|
||||
$obj = $this;
|
||||
|
||||
foreach ($idList as $id) {
|
||||
$obj = $obj->withAddedId($id);
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with an added item.
|
||||
*/
|
||||
public function withAdded(LinkMultipleItem $item): self
|
||||
{
|
||||
return $this->withAddedList([$item]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with an added item list.
|
||||
* .
|
||||
* @param LinkMultipleItem[] $list
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function withAddedList(array $list): self
|
||||
{
|
||||
$newList = $this->list;
|
||||
|
||||
foreach ($list as $item) {
|
||||
$index = $this->searchIdInList($item->getId());
|
||||
|
||||
if ($index !== null) {
|
||||
$newList[$index] = $item;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$newList[] = $item;
|
||||
}
|
||||
|
||||
return self::create($newList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with removed item.
|
||||
*/
|
||||
public function withRemoved(LinkMultipleItem $item): self
|
||||
{
|
||||
return $this->withRemovedById($item->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with removed item by ID.
|
||||
*/
|
||||
public function withRemovedById(string $id): self
|
||||
{
|
||||
$newList = $this->list;
|
||||
|
||||
$index = $this->searchIdInList($id);
|
||||
|
||||
if ($index !== null) {
|
||||
unset($newList[$index]);
|
||||
|
||||
$newList = array_values($newList);
|
||||
}
|
||||
|
||||
return self::create($newList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create with an optional item list.
|
||||
*
|
||||
* @param LinkMultipleItem[] $list
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function create(array $list = []): self
|
||||
{
|
||||
return new self($list);
|
||||
}
|
||||
|
||||
private function validateList(): void
|
||||
{
|
||||
$idList = [];
|
||||
|
||||
foreach ($this->list as $item) {
|
||||
if (!$item instanceof LinkMultipleItem) {
|
||||
throw new RuntimeException("Bad item.");
|
||||
}
|
||||
|
||||
if (in_array($item->getId(), $idList)) {
|
||||
throw new RuntimeException("List contains duplicates.");
|
||||
}
|
||||
|
||||
$idList[] = strtolower($item->getId());
|
||||
}
|
||||
}
|
||||
|
||||
private function searchIdInList(string $id): ?int
|
||||
{
|
||||
foreach ($this->getIdList() as $i => $item) {
|
||||
if ($item === $id) {
|
||||
return $i;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\LinkMultiple;
|
||||
|
||||
use Espo\ORM\Value\AttributeExtractor;
|
||||
|
||||
use Espo\Core\Field\LinkMultiple;
|
||||
|
||||
use stdClass;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @implements AttributeExtractor<LinkMultiple>
|
||||
*/
|
||||
class LinkMultipleAttributeExtractor implements AttributeExtractor
|
||||
{
|
||||
/**
|
||||
* @param LinkMultiple $value
|
||||
*/
|
||||
public function extract(object $value, string $field): stdClass
|
||||
{
|
||||
if (!$value instanceof LinkMultiple) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
$nameMap = (object) [];
|
||||
$columnData = (object) [];
|
||||
|
||||
foreach ($value->getList() as $item) {
|
||||
$id = $item->getId();
|
||||
|
||||
$nameMap->$id = $item->getName();
|
||||
|
||||
$columnItemData = (object) [];
|
||||
|
||||
foreach ($item->getColumnList() as $column) {
|
||||
$columnItemData->$column = $item->getColumnValue($column);
|
||||
}
|
||||
|
||||
$columnData->$id = $columnItemData;
|
||||
}
|
||||
|
||||
return (object) [
|
||||
$field . 'Ids' => $value->getIdList(),
|
||||
$field . 'Names' => $nameMap,
|
||||
$field . 'Columns' => $columnData,
|
||||
];
|
||||
}
|
||||
|
||||
public function extractFromNull(string $field): stdClass
|
||||
{
|
||||
return (object) [
|
||||
$field . 'Ids' => [],
|
||||
$field . 'Names' => (object) [],
|
||||
$field . 'Columns' => (object) [],
|
||||
];
|
||||
}
|
||||
}
|
||||
179
application/Espo/Core/Field/LinkMultiple/LinkMultipleFactory.php
Normal file
179
application/Espo/Core/Field/LinkMultiple/LinkMultipleFactory.php
Normal file
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\LinkMultiple;
|
||||
|
||||
use Espo\Core\ORM\Type\FieldType;
|
||||
use Espo\ORM\Defs;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Value\ValueFactory;
|
||||
|
||||
use Espo\Core\Field\LinkMultiple;
|
||||
use Espo\Core\Field\LinkMultipleItem;
|
||||
use Espo\Core\ORM\Entity as CoreEntity;
|
||||
|
||||
use RuntimeException;
|
||||
use InvalidArgumentException;
|
||||
use stdClass;
|
||||
|
||||
class LinkMultipleFactory implements ValueFactory
|
||||
{
|
||||
public function __construct(private Defs $ormDefs, private EntityManager $entityManager)
|
||||
{}
|
||||
|
||||
public function isCreatableFromEntity(Entity $entity, string $field): bool
|
||||
{
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
$entityDefs = $this->ormDefs->getEntity($entityType);
|
||||
|
||||
if (!$entityDefs->hasField($field)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $entityDefs->getField($field)->getType() === FieldType::LINK_MULTIPLE;
|
||||
}
|
||||
|
||||
public function createFromEntity(Entity $entity, string $field): LinkMultiple
|
||||
{
|
||||
if (!$this->isCreatableFromEntity($entity, $field)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
if (!$entity instanceof CoreEntity) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
$itemList = [];
|
||||
|
||||
if (!$entity->has($field . 'Ids') && !$entity->isNew()) {
|
||||
$this->loadLinkMultipleField($entity, $field);
|
||||
}
|
||||
|
||||
$idList = $entity->getLinkMultipleIdList($field);
|
||||
|
||||
$nameMap = $entity->get($field . 'Names') ?? (object) [];
|
||||
|
||||
$columnData = null;
|
||||
|
||||
if ($entity->hasAttribute($field . 'Columns')) {
|
||||
$columnData = $entity->get($field . 'Columns') ?
|
||||
$entity->get($field . 'Columns') :
|
||||
$this->loadColumnData($entity, $field);
|
||||
}
|
||||
|
||||
foreach ($idList as $id) {
|
||||
$item = LinkMultipleItem::create($id);
|
||||
|
||||
if ($columnData && property_exists($columnData, $id)) {
|
||||
$item = $this->addColumnValues($item, $columnData->$id);
|
||||
}
|
||||
|
||||
$name = $nameMap->$id ?? null;
|
||||
|
||||
if ($name !== null) {
|
||||
$item = $item->withName($name);
|
||||
}
|
||||
|
||||
$itemList[] = $item;
|
||||
}
|
||||
|
||||
return new LinkMultiple($itemList);
|
||||
}
|
||||
|
||||
private function loadLinkMultipleField(CoreEntity $entity, string $field): void
|
||||
{
|
||||
$entity->loadLinkMultipleField($field);
|
||||
}
|
||||
|
||||
private function loadColumnData(Entity $entity, string $field): stdClass
|
||||
{
|
||||
if ($entity->isNew()) {
|
||||
return (object) [];
|
||||
}
|
||||
|
||||
$columnData = (object) [];
|
||||
|
||||
$select = [Attribute::ID];
|
||||
|
||||
$entityDefs = $this->ormDefs->getEntity($entity->getEntityType());
|
||||
|
||||
$columns = $entityDefs->getField($field)->getParam('columns') ?? [];
|
||||
|
||||
if (count($columns) === 0) {
|
||||
return $columnData;
|
||||
}
|
||||
|
||||
$foreignEntityType = $entityDefs->tryGetRelation($field)?->tryGetForeignEntityType();
|
||||
|
||||
if ($foreignEntityType) {
|
||||
$foreignEntityDefs = $this->entityManager->getDefs()->getEntity($foreignEntityType);
|
||||
|
||||
foreach ($columns as $column => $attribute) {
|
||||
if (!$foreignEntityDefs->hasAttribute($attribute)) {
|
||||
// For backward compatibility. If foreign attributes defined in the field do not exist.
|
||||
unset($columns[$column]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($columns as $item) {
|
||||
$select[] = $item;
|
||||
}
|
||||
|
||||
$collection = $this->entityManager
|
||||
->getRDBRepository($entity->getEntityType())
|
||||
->getRelation($entity, $field)
|
||||
->select($select)
|
||||
->find();
|
||||
|
||||
foreach ($collection as $itemEntity) {
|
||||
$id = $itemEntity->getId();
|
||||
|
||||
$columnData->$id = (object) [];
|
||||
|
||||
foreach ($columns as $column => $attribute) {
|
||||
$columnData->$id->$column = $itemEntity->get($attribute);
|
||||
}
|
||||
}
|
||||
|
||||
return $columnData;
|
||||
}
|
||||
|
||||
private function addColumnValues(LinkMultipleItem $item, stdClass $data): LinkMultipleItem
|
||||
{
|
||||
foreach (get_object_vars($data) as $column => $value) {
|
||||
$item = $item->withColumnValue($column, $value);
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
146
application/Espo/Core/Field/LinkMultipleItem.php
Normal file
146
application/Espo/Core/Field/LinkMultipleItem.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* A link-multiple item. Immutable.
|
||||
*/
|
||||
class LinkMultipleItem
|
||||
{
|
||||
private string $id;
|
||||
private ?string $name = null;
|
||||
/** @var array<string, mixed> */
|
||||
private array $columnData = [];
|
||||
|
||||
/**
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function __construct(string $id)
|
||||
{
|
||||
if ($id === '') {
|
||||
throw new RuntimeException("Empty ID.");
|
||||
}
|
||||
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an ID.
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a name.
|
||||
*/
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a column value.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getColumnValue(string $column)
|
||||
{
|
||||
return $this->columnData[$column] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a column value is set.
|
||||
*/
|
||||
public function hasColumnValue(string $column): bool
|
||||
{
|
||||
return array_key_exists($column, $this->columnData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of set columns.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function getColumnList(): array
|
||||
{
|
||||
return array_keys($this->columnData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a name.
|
||||
*/
|
||||
public function withName(string $name): self
|
||||
{
|
||||
$obj = $this->clone();
|
||||
$obj->name = $name;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a column value.
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function withColumnValue(string $column, $value): self
|
||||
{
|
||||
$obj = $this->clone();
|
||||
$obj->columnData[$column] = $value;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function create(string $id, ?string $name = null): self
|
||||
{
|
||||
$obj = new self($id);
|
||||
$obj->name = $name;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
private function clone(): self
|
||||
{
|
||||
$obj = new self($this->id);
|
||||
|
||||
$obj->name = $this->name;
|
||||
$obj->columnData = $this->columnData;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
109
application/Espo/Core/Field/LinkParent.php
Normal file
109
application/Espo/Core/Field/LinkParent.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* A link-parent value object. Immutable.
|
||||
*/
|
||||
class LinkParent
|
||||
{
|
||||
private string $entityType;
|
||||
private string $id;
|
||||
private ?string $name = null;
|
||||
|
||||
public function __construct(string $entityType, string $id)
|
||||
{
|
||||
if (!$entityType) {
|
||||
throw new RuntimeException("Empty entity type.");
|
||||
}
|
||||
|
||||
if (!$id) {
|
||||
throw new RuntimeException("Empty ID.");
|
||||
}
|
||||
|
||||
$this->entityType = $entityType;
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an ID.
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an entity type.
|
||||
*/
|
||||
public function getEntityType(): string
|
||||
{
|
||||
return $this->entityType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a name.
|
||||
*/
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a name.
|
||||
*/
|
||||
public function withName(?string $name): self
|
||||
{
|
||||
$obj = new self($this->entityType, $this->id);
|
||||
|
||||
$obj->name = $name;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create.
|
||||
*/
|
||||
public static function create(string $entityType, string $id): self
|
||||
{
|
||||
return new self($entityType, $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from an entity.
|
||||
*/
|
||||
public static function createFromEntity(Entity $entity): self
|
||||
{
|
||||
return new self($entity->getEntityType(), $entity->getId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\LinkParent;
|
||||
|
||||
use Espo\ORM\Value\AttributeExtractor;
|
||||
|
||||
use Espo\Core\Field\LinkParent;
|
||||
|
||||
use stdClass;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @implements AttributeExtractor<LinkParent>
|
||||
*/
|
||||
class LinkParentAttributeExtractor implements AttributeExtractor
|
||||
{
|
||||
/**
|
||||
* @param LinkParent $value
|
||||
*/
|
||||
public function extract(object $value, string $field): stdClass
|
||||
{
|
||||
if (!$value instanceof LinkParent) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
return (object) [
|
||||
$field . 'Id' => $value->getId(),
|
||||
$field . 'Type' => $value->getEntityType(),
|
||||
$field . 'Name' => $value->getName(),
|
||||
];
|
||||
}
|
||||
|
||||
public function extractFromNull(string $field): stdClass
|
||||
{
|
||||
return (object) [
|
||||
$field . 'Id' => null,
|
||||
$field . 'Type' => null,
|
||||
$field . 'Name' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
58
application/Espo/Core/Field/LinkParent/LinkParentFactory.php
Normal file
58
application/Espo/Core/Field/LinkParent/LinkParentFactory.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\LinkParent;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\Value\ValueFactory;
|
||||
use Espo\Core\Field\LinkParent;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class LinkParentFactory implements ValueFactory
|
||||
{
|
||||
public function isCreatableFromEntity(Entity $entity, string $field): bool
|
||||
{
|
||||
return $entity->get($field . 'Id') !== null && $entity->get($field . 'Type') !== null;
|
||||
}
|
||||
|
||||
public function createFromEntity(Entity $entity, string $field): LinkParent
|
||||
{
|
||||
if (!$this->isCreatableFromEntity($entity, $field)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$id = $entity->get($field . 'Id');
|
||||
$entityType = $entity->get($field . 'Type');
|
||||
|
||||
return LinkParent
|
||||
::create($entityType, $id)
|
||||
->withName($entity->get($field . 'Name'));
|
||||
}
|
||||
}
|
||||
171
application/Espo/Core/Field/PhoneNumber.php
Normal file
171
application/Espo/Core/Field/PhoneNumber.php
Normal file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* A phone number value. Immutable.
|
||||
*/
|
||||
class PhoneNumber
|
||||
{
|
||||
private string $number;
|
||||
private ?string $type = null;
|
||||
private bool $isOptedOut = false;
|
||||
private bool $isInvalid = false;
|
||||
|
||||
public function __construct(string $number)
|
||||
{
|
||||
if ($number === '') {
|
||||
throw new RuntimeException("Empty phone number.");
|
||||
}
|
||||
|
||||
$this->number = $number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a type.
|
||||
*/
|
||||
public function getType(): ?string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a number.
|
||||
*/
|
||||
public function getNumber(): string
|
||||
{
|
||||
return $this->number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether opted-out.
|
||||
*/
|
||||
public function isOptedOut(): bool
|
||||
{
|
||||
return $this->isOptedOut;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether invalid.
|
||||
*/
|
||||
public function isInvalid(): bool
|
||||
{
|
||||
return $this->isInvalid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a type.
|
||||
*/
|
||||
public function withType(string $type): self
|
||||
{
|
||||
$obj = $this->clone();
|
||||
|
||||
$obj->type = $type;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone set invalid.
|
||||
*/
|
||||
public function invalid(): self
|
||||
{
|
||||
$obj = $this->clone();
|
||||
|
||||
$obj->isInvalid = true;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone set not invalid.
|
||||
*/
|
||||
public function notInvalid(): self
|
||||
{
|
||||
$obj = $this->clone();
|
||||
|
||||
$obj->isInvalid = false;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone set opted-out.
|
||||
*/
|
||||
public function optedOut(): self
|
||||
{
|
||||
$obj = $this->clone();
|
||||
|
||||
$obj->isOptedOut = true;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone set not opted-out.
|
||||
*/
|
||||
public function notOptedOut(): self
|
||||
{
|
||||
$obj = $this->clone();
|
||||
|
||||
$obj->isOptedOut = false;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create with a number.
|
||||
*/
|
||||
public static function create(string $number): self
|
||||
{
|
||||
return new self($number);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from a number and type.
|
||||
*/
|
||||
public static function createWithType(string $number, string $type): self
|
||||
{
|
||||
return self::create($number)->withType($type);
|
||||
}
|
||||
|
||||
private function clone(): self
|
||||
{
|
||||
$obj = new self($this->number);
|
||||
|
||||
$obj->type = $this->type;
|
||||
$obj->isInvalid = $this->isInvalid;
|
||||
$obj->isOptedOut = $this->isOptedOut;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\PhoneNumber;
|
||||
|
||||
use Espo\ORM\Value\AttributeExtractor;
|
||||
|
||||
use Espo\Core\Field\PhoneNumberGroup;
|
||||
|
||||
use stdClass;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @implements AttributeExtractor<PhoneNumberGroup>
|
||||
*/
|
||||
class PhoneNumberGroupAttributeExtractor implements AttributeExtractor
|
||||
{
|
||||
/**
|
||||
* @param PhoneNumberGroup $group
|
||||
*/
|
||||
public function extract(object $group, string $field): stdClass
|
||||
{
|
||||
if (!$group instanceof PhoneNumberGroup) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
$primaryNumber = $group->getPrimary() ? $group->getPrimary()->getNumber() : null;
|
||||
|
||||
$dataList = [];
|
||||
|
||||
foreach ($group->getList() as $phoneNumber) {
|
||||
$dataList[] = (object) [
|
||||
'phoneNumber' => $phoneNumber->getNumber(),
|
||||
'type' => $phoneNumber->getType(),
|
||||
'primary' => $primaryNumber && $phoneNumber->getNumber() === $primaryNumber,
|
||||
'optOut' => $phoneNumber->isOptedOut(),
|
||||
'invalid' => $phoneNumber->isInvalid(),
|
||||
];
|
||||
}
|
||||
|
||||
return (object) [
|
||||
$field => $primaryNumber,
|
||||
$field . 'Data' => $dataList,
|
||||
];
|
||||
}
|
||||
|
||||
public function extractFromNull(string $field): stdClass
|
||||
{
|
||||
return (object) [
|
||||
$field => null,
|
||||
$field . 'Data' => [],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field\PhoneNumber;
|
||||
|
||||
use Espo\Core\ORM\Type\FieldType;
|
||||
use Espo\Entities\PhoneNumber as PhoneNumberEntity;
|
||||
use Espo\ORM\Defs\Params\FieldParam;
|
||||
use Espo\Repositories\PhoneNumber as Repository;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Value\ValueFactory;
|
||||
|
||||
use Espo\Core\Field\PhoneNumber;
|
||||
use Espo\Core\Field\PhoneNumberGroup;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
|
||||
use RuntimeException;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* A phone number group factory.
|
||||
*/
|
||||
class PhoneNumberGroupFactory implements ValueFactory
|
||||
{
|
||||
private Metadata $metadata;
|
||||
private EntityManager $entityManager;
|
||||
|
||||
/**
|
||||
* @todo Use OrmDefs instead of Metadata.
|
||||
*/
|
||||
public function __construct(Metadata $metadata, EntityManager $entityManager)
|
||||
{
|
||||
$this->metadata = $metadata;
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
public function isCreatableFromEntity(Entity $entity, string $field): bool
|
||||
{
|
||||
$type = $this->metadata->get(['entityDefs', $entity->getEntityType(), 'fields', $field, FieldParam::TYPE]);
|
||||
|
||||
if ($type !== FieldType::PHONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function createFromEntity(Entity $entity, string $field): PhoneNumberGroup
|
||||
{
|
||||
if (!$this->isCreatableFromEntity($entity, $field)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$phoneNumberList = [];
|
||||
|
||||
$primaryPhoneNumber = null;
|
||||
|
||||
$dataList = null;
|
||||
|
||||
$dataAttribute = $field . 'Data';
|
||||
|
||||
if ($entity->has($dataAttribute)) {
|
||||
$dataList = $this->sanitizeDataList(
|
||||
$entity->get($dataAttribute)
|
||||
);
|
||||
}
|
||||
|
||||
if (!$dataList && $entity->has($field) && !$entity->get($field)) {
|
||||
$dataList = [];
|
||||
}
|
||||
|
||||
if (!$dataList) {
|
||||
/** @var Repository $repository */
|
||||
$repository = $this->entityManager->getRepository(PhoneNumberEntity::ENTITY_TYPE);
|
||||
|
||||
$dataList = $repository->getPhoneNumberData($entity);
|
||||
}
|
||||
|
||||
foreach ($dataList as $item) {
|
||||
$phoneNumber = PhoneNumber::create($item->phoneNumber);
|
||||
|
||||
if ($item->type ?? false) {
|
||||
$phoneNumber = $phoneNumber->withType($item->type);
|
||||
}
|
||||
|
||||
if ($item->optOut ?? false) {
|
||||
$phoneNumber = $phoneNumber->optedOut();
|
||||
}
|
||||
|
||||
if ($item->invalid ?? false) {
|
||||
$phoneNumber = $phoneNumber->invalid();
|
||||
}
|
||||
|
||||
if ($item->primary ?? false) {
|
||||
$primaryPhoneNumber = $phoneNumber;
|
||||
}
|
||||
|
||||
$phoneNumberList[] = $phoneNumber;
|
||||
}
|
||||
|
||||
$group = PhoneNumberGroup::create($phoneNumberList);
|
||||
|
||||
if ($primaryPhoneNumber) {
|
||||
$group = $group->withPrimary($primaryPhoneNumber);
|
||||
}
|
||||
|
||||
return $group;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>|stdClass> $dataList
|
||||
* @return stdClass[]
|
||||
*/
|
||||
private function sanitizeDataList(array $dataList): array
|
||||
{
|
||||
$sanitizedDataList = [];
|
||||
|
||||
foreach ($dataList as $item) {
|
||||
if (is_array($item)) {
|
||||
$sanitizedDataList[] = (object) $item;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_object($item)) {
|
||||
throw new RuntimeException("Bad data.");
|
||||
}
|
||||
|
||||
$sanitizedDataList[] = $item;
|
||||
}
|
||||
|
||||
return $sanitizedDataList;
|
||||
}
|
||||
}
|
||||
299
application/Espo/Core/Field/PhoneNumberGroup.php
Normal file
299
application/Espo/Core/Field/PhoneNumberGroup.php
Normal file
@@ -0,0 +1,299 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Field;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* A phone number group. Contains a list of phone numbers. One phone number is set as primary.
|
||||
* If not empty, then there always should be a primary number. Immutable.
|
||||
*/
|
||||
class PhoneNumberGroup
|
||||
{
|
||||
/** @var PhoneNumber[] */
|
||||
private $list = [];
|
||||
private ?PhoneNumber $primary = null;
|
||||
|
||||
/**
|
||||
* @param PhoneNumber[] $list
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function __construct(array $list = [])
|
||||
{
|
||||
foreach ($list as $item) {
|
||||
$this->list[] = clone $item;
|
||||
}
|
||||
|
||||
$this->validateList();
|
||||
|
||||
if (count($this->list) !== 0) {
|
||||
$this->primary = $this->list[0];
|
||||
}
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$newList = [];
|
||||
|
||||
foreach ($this->list as $item) {
|
||||
$newList[] = clone $item;
|
||||
}
|
||||
|
||||
$this->list = $newList;
|
||||
|
||||
if ($this->primary) {
|
||||
$this->primary = clone $this->primary;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a primary number as a string. If no primary, then returns null,
|
||||
*/
|
||||
public function getPrimaryNumber(): ?string
|
||||
{
|
||||
$primary = $this->getPrimary();
|
||||
|
||||
if (!$primary) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $primary->getNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a primary phone number.
|
||||
*/
|
||||
public function getPrimary(): ?PhoneNumber
|
||||
{
|
||||
if ($this->isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->primary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all phone numbers.
|
||||
*
|
||||
* @return PhoneNumber[]
|
||||
*/
|
||||
public function getList(): array
|
||||
{
|
||||
return $this->list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a number of phone numbers.
|
||||
*/
|
||||
public function getCount(): int
|
||||
{
|
||||
return count($this->list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of phone numbers w/o a primary.
|
||||
*
|
||||
* @return PhoneNumber[]
|
||||
*/
|
||||
public function getSecondaryList(): array
|
||||
{
|
||||
$list = [];
|
||||
|
||||
foreach ($this->list as $item) {
|
||||
if ($item === $this->primary) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$list[] = $item;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of phone numbers represented as strings.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getNumberList(): array
|
||||
{
|
||||
$list = [];
|
||||
|
||||
foreach ($this->list as $item) {
|
||||
$list[] = $item->getNumber();
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a phone number by number represented as a string.
|
||||
*/
|
||||
public function getByNumber(string $number): ?PhoneNumber
|
||||
{
|
||||
$index = $this->searchNumberInList($number);
|
||||
|
||||
if ($index === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->list[$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an number is in the list.
|
||||
*/
|
||||
public function hasNumber(string $number): bool
|
||||
{
|
||||
return in_array($number, $this->getNumberList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with another primary phone number.
|
||||
*/
|
||||
public function withPrimary(PhoneNumber $phoneNumber): self
|
||||
{
|
||||
$list = $this->list;
|
||||
|
||||
$index = $this->searchNumberInList($phoneNumber->getNumber());
|
||||
|
||||
if ($index !== null) {
|
||||
unset($list[$index]);
|
||||
|
||||
$list = array_values($list);
|
||||
}
|
||||
|
||||
$newList = array_merge([$phoneNumber], $list);
|
||||
|
||||
return self::create($newList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with an added phone number list.
|
||||
*
|
||||
* @param PhoneNumber[] $list
|
||||
*/
|
||||
public function withAddedList(array $list): self
|
||||
{
|
||||
$newList = $this->list;
|
||||
|
||||
foreach ($list as $item) {
|
||||
$index = $this->searchNumberInList($item->getNumber());
|
||||
|
||||
if ($index !== null) {
|
||||
$newList[$index] = $item;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$newList[] = $item;
|
||||
}
|
||||
|
||||
return self::create($newList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with an added phone number.
|
||||
*/
|
||||
public function withAdded(PhoneNumber $phoneNumber): self
|
||||
{
|
||||
return $this->withAddedList([$phoneNumber]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with removed phone number.
|
||||
*/
|
||||
public function withRemoved(PhoneNumber $phoneNumber): self
|
||||
{
|
||||
return $this->withRemovedByNumber($phoneNumber->getNumber());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with removed phone number passed by a number.
|
||||
*/
|
||||
public function withRemovedByNumber(string $number): self
|
||||
{
|
||||
$newList = $this->list;
|
||||
|
||||
$index = $this->searchNumberInList($number);
|
||||
|
||||
if ($index !== null) {
|
||||
unset($newList[$index]);
|
||||
|
||||
$newList = array_values($newList);
|
||||
}
|
||||
|
||||
return self::create($newList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create with an optional phone number list. A first item will be set as primary.
|
||||
*
|
||||
* @param PhoneNumber[] $list
|
||||
*/
|
||||
public static function create(array $list = []): self
|
||||
{
|
||||
return new self($list);
|
||||
}
|
||||
|
||||
private function searchNumberInList(string $number): ?int
|
||||
{
|
||||
foreach ($this->getNumberList() as $i => $item) {
|
||||
if ($item === $number) {
|
||||
return $i;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function validateList(): void
|
||||
{
|
||||
$numberList = [];
|
||||
|
||||
foreach ($this->list as $item) {
|
||||
if (!$item instanceof PhoneNumber) {
|
||||
throw new RuntimeException("Bad item.");
|
||||
}
|
||||
|
||||
if (in_array($item->getNumber(), $numberList)) {
|
||||
throw new RuntimeException("Number list contains a duplicate.");
|
||||
}
|
||||
|
||||
$numberList[] = strtolower($item->getNumber());
|
||||
}
|
||||
}
|
||||
|
||||
private function isEmpty(): bool
|
||||
{
|
||||
return count($this->list) === 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user