Initial commit

This commit is contained in:
root
2026-01-19 17:44:46 +01:00
commit 823af8b11d
8721 changed files with 1130846 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
<?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\Tools\Export\Format;
use Espo\Core\Field\Currency;
use Espo\Core\Field\Date;
use Espo\Core\Field\DateTime;
use Espo\ORM\Entity;
interface CellValuePreparator
{
/**
* @param string $name A field name.
*/
public function prepare(
Entity $entity,
string $name
): string|bool|int|float|Date|DateTime|Currency|null;
}

View File

@@ -0,0 +1,52 @@
<?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\Tools\Export\Format;
use Espo\Core\InjectableFactory;
use Espo\Core\Utils\Metadata;
use Espo\Tools\Export\Format\Xlsx\CellValuePreparators\General;
class CellValuePreparatorFactory
{
public function __construct(
private InjectableFactory $injectableFactory,
private Metadata $metadata
) {}
public function create(string $format, string $fieldType): CellValuePreparator
{
/** @var class-string<CellValuePreparator> $className */
$className = $this->metadata
->get(['app', 'export', 'formatDefs', $format, 'cellValuePreparatorClassNameMap', $fieldType]) ??
General::class;
return $this->injectableFactory->create($className);
}
}

View File

@@ -0,0 +1,65 @@
<?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\Tools\Export\Format\Csv;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\Core\ORM\Type\FieldType;
use Espo\Core\Utils\Metadata;
use Espo\ORM\Entity;
use Espo\Tools\Export\AdditionalFieldsLoader as AdditionalFieldsLoaderInterface;
/**
* @noinspection PhpUnused
*/
class AdditionalFieldsLoader implements AdditionalFieldsLoaderInterface
{
public function __construct(private Metadata $metadata) {}
public function load(Entity $entity, array $fieldList): void
{
if (!$entity instanceof CoreEntity) {
return;
}
foreach ($fieldList as $field) {
$fieldType = $this->metadata
->get(['entityDefs', $entity->getEntityType(), 'fields', $field, 'type']);
if (
$fieldType === FieldType::LINK_MULTIPLE ||
$fieldType === FieldType::ATTACHMENT_MULTIPLE
) {
if (!$entity->has($field . 'Ids') && $entity->hasLinkMultipleField($field)) {
$entity->loadLinkMultipleField($field);
}
}
}
}
}

View File

@@ -0,0 +1,123 @@
<?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\Tools\Export\Format\Csv;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Json;
use Espo\Entities\Preferences;
use Espo\ORM\Entity;
use Espo\Tools\Export\Collection;
use Espo\Tools\Export\Processor as ProcessorInterface;
use Espo\Tools\Export\Processor\Params;
use Psr\Http\Message\StreamInterface;
use GuzzleHttp\Psr7\Stream;
use RuntimeException;
use const JSON_UNESCAPED_UNICODE;
class Processor implements ProcessorInterface
{
public function __construct(
private Config $config,
private Preferences $preferences
) {}
public function process(Params $params, Collection $collection): StreamInterface
{
$attributeList = $params->getAttributeList();
$delimiterRaw =
$this->preferences->get('exportDelimiter') ??
$this->config->get('exportDelimiter') ??
',';
$delimiter = str_replace('\t', "\t", $delimiterRaw);
$fp = fopen('php://temp', 'w');
if ($fp === false) {
throw new RuntimeException("Could not open temp.");
}
fputcsv($fp, $attributeList, $delimiter);
foreach ($collection as $entity) {
$preparedRow = $this->prepareRow($entity, $attributeList);
fputcsv($fp, $preparedRow, $delimiter, '"' , "\0");
}
rewind($fp);
return new Stream($fp);
}
/**
* @param string[] $attributeList
* @return string[]
*/
private function prepareRow(Entity $entity, array $attributeList): array
{
$preparedRow = [];
foreach ($attributeList as $attribute) {
$value = $entity->get($attribute);
if (is_array($value) || is_object($value)) {
$value = Json::encode($value, JSON_UNESCAPED_UNICODE);
}
$value = (string) $value;
$preparedRow[] = $this->sanitizeCellValue($value);
}
return $preparedRow;
}
private function sanitizeCellValue(string $value): string
{
if ($value === '') {
return $value;
}
if (is_numeric($value)) {
return $value;
}
if (in_array($value[0], ['+', '-', '@', '='])) {
return "'" . $value;
}
return $value;
}
}

View File

@@ -0,0 +1,89 @@
<?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\Tools\Export\Format\Xlsx;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\Core\ORM\Type\FieldType;
use Espo\Core\Utils\Metadata;
use Espo\ORM\Defs\Params\RelationParam;
use Espo\ORM\Entity;
use Espo\Tools\Export\AdditionalFieldsLoader as AdditionalFieldsLoaderInterface;
/**
* @noinspection PhpUnused
*/
class AdditionalFieldsLoader implements AdditionalFieldsLoaderInterface
{
public function __construct(private Metadata $metadata)
{}
public function load(Entity $entity, array $fieldList): void
{
if (!$entity instanceof CoreEntity) {
return;
}
foreach ($entity->getRelationList() as $link) {
if (!in_array($link, $fieldList)) {
continue;
}
if ($entity->getRelationType($link) === Entity::BELONGS_TO_PARENT) {
if (!$entity->get($link . 'Name')) {
$entity->loadParentNameField($link);
}
} else if (
(
(
$entity->getRelationType($link) === Entity::BELONGS_TO &&
$entity->getRelationParam($link, RelationParam::NO_JOIN)
) ||
$entity->getRelationType($link) === Entity::HAS_ONE
) &&
$entity->hasAttribute($link . 'Name')
) {
if (!$entity->get($link . 'Name') || !$entity->get($link . 'Id')) {
$entity->loadLinkField($link);
}
}
}
foreach ($fieldList as $field) {
$fieldType = $this->metadata
->get(['entityDefs', $entity->getEntityType(), 'fields', $field, 'type']);
if ($fieldType === FieldType::LINK_MULTIPLE || $fieldType === FieldType::ATTACHMENT_MULTIPLE) {
if (!$entity->has($field . 'Ids') && $entity->hasLinkMultipleField($field)) {
$entity->loadLinkMultipleField($field);
}
}
}
}
}

View File

@@ -0,0 +1,51 @@
<?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\Tools\Export\Format\Xlsx\CellValuePreparators;
use Espo\Core\Field\Address\AddressFactory;
use Espo\Core\Field\Address\AddressFormatterFactory;
use Espo\ORM\Entity;
use Espo\Tools\Export\Format\CellValuePreparator;
class Address implements CellValuePreparator
{
public function __construct(
private AddressFormatterFactory $formatterFactory
) {}
public function prepare(Entity $entity, string $name): ?string
{
$address = (new AddressFactory())->createFromEntity($entity, $name);
$formatter = $this->formatterFactory->createDefault();
return $formatter->format($address) ?: null;
}
}

View File

@@ -0,0 +1,41 @@
<?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\Tools\Export\Format\Xlsx\CellValuePreparators;
use Espo\ORM\Entity;
use Espo\Tools\Export\Format\CellValuePreparator;
class Boolean implements CellValuePreparator
{
public function prepare(Entity $entity, string $name): bool
{
return (bool) $entity->get($name);
}
}

View File

@@ -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\Tools\Export\Format\Xlsx\CellValuePreparators;
use Espo\Core\Field\Currency as CurrencyValue;
use Espo\Core\Field\Currency\CurrencyFactory;
use Espo\ORM\Entity;
use Espo\Tools\Export\Format\CellValuePreparator;
class Currency implements CellValuePreparator
{
public function prepare(Entity $entity, string $name): ?CurrencyValue
{
$factory = new CurrencyFactory();
if (!$factory->isCreatableFromEntity($entity, $name)) {
return null;
}
return $factory->createFromEntity($entity, $name);
}
}

View 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\Tools\Export\Format\Xlsx\CellValuePreparators;
use Espo\Core\Field\Currency as CurrencyValue;
use Espo\Core\Utils\Config;
use Espo\ORM\Entity;
use Espo\Tools\Export\Format\CellValuePreparator;
class CurrencyConverted implements CellValuePreparator
{
private string $code;
public function __construct(Config $config)
{
$this->code = $config->get('defaultCurrency');
}
public function prepare(Entity $entity, string $name): ?CurrencyValue
{
$value = $entity->get($name);
if ($value === null) {
return null;
}
return CurrencyValue::create($value, $this->code);
}
}

View File

@@ -0,0 +1,48 @@
<?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\Tools\Export\Format\Xlsx\CellValuePreparators;
use Espo\Core\Field\Date as DateValue;
use Espo\ORM\Entity;
use Espo\Tools\Export\Format\CellValuePreparator;
class Date implements CellValuePreparator
{
public function prepare(Entity $entity, string $name): ?DateValue
{
$value = $entity->get($name);
if (!$value) {
return null;
}
return DateValue::fromString($value);
}
}

View File

@@ -0,0 +1,61 @@
<?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\Tools\Export\Format\Xlsx\CellValuePreparators;
use Espo\Core\Field\DateTime as DateTimeValue;
use Espo\Core\Utils\Config;
use Espo\ORM\Entity;
use Espo\Tools\Export\Format\CellValuePreparator;
use DateTimeZone;
class DateTime implements CellValuePreparator
{
private string $timezone;
public function __construct(Config\ApplicationConfig $applicationConfig)
{
$this->timezone = $applicationConfig->getTimeZone();
}
public function prepare(Entity $entity, string $name): ?DateTimeValue
{
$value = $entity->get($name);
if (!$value) {
return null;
}
return DateTimeValue::fromString($value)
->withTimezone(
new DateTimeZone($this->timezone)
);
}
}

View File

@@ -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\Tools\Export\Format\Xlsx\CellValuePreparators;
use Espo\Core\Field\DateTime as DateTimeValue;
use Espo\Core\Field\Date as DateValue;
use Espo\Core\Utils\Config;
use Espo\ORM\Entity;
use Espo\Tools\Export\Format\CellValuePreparator;
use DateTimeZone;
class DateTimeOptional implements CellValuePreparator
{
private string $timezone;
public function __construct(Config\ApplicationConfig $applicationConfig)
{
$this->timezone = $applicationConfig->getTimeZone();
}
public function prepare(Entity $entity, string $name): DateTimeValue|DateValue|null
{
$dateValue = $entity->get($name . 'Date');
if ($dateValue !== null) {
return DateValue::fromString($dateValue);
}
$value = $entity->get($name);
if (!$value) {
return null;
}
return DateTimeValue::fromString($value)
->withTimezone(
new DateTimeZone($this->timezone)
);
}
}

View File

@@ -0,0 +1,81 @@
<?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\Tools\Export\Format\Xlsx\CellValuePreparators;
use Espo\Core\Utils\Language;
use Espo\ORM\Entity;
use Espo\Tools\Export\Format\CellValuePreparator;
class Duration implements CellValuePreparator
{
public function __construct(private Language $language)
{}
public function prepare(Entity $entity, string $name): ?string
{
$value = $entity->get($name);
if (!$value) {
return null;
}
$seconds = intval($value);
$days = intval(floor($seconds / 86400));
$seconds = $seconds - $days * 86400;
$hours = intval(floor($seconds / 3600));
$seconds = $seconds - $hours * 3600;
$minutes = intval(floor($seconds / 60));
$value = '';
if ($days) {
$value .= $days . $this->language->translateLabel('d', 'durationUnits');
if ($minutes || $hours) {
$value .= ' ';
}
}
if ($hours) {
$value .= $hours . $this->language->translateLabel('h', 'durationUnits');
if ($minutes) {
$value .= ' ';
}
}
if ($minutes) {
$value .= $minutes . $this->language->translateLabel('m', 'durationUnits');
}
return $value;
}
}

View File

@@ -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\Tools\Export\Format\Xlsx\CellValuePreparators;
use Espo\Core\Utils\Language;
use Espo\ORM\Defs;
use Espo\ORM\Entity;
use Espo\Tools\Export\Format\CellValuePreparator;
use Espo\Tools\Export\Format\Xlsx\FieldHelper;
class Enumeration implements CellValuePreparator
{
public function __construct(
private Defs $ormDefs,
private Language $language,
private FieldHelper $fieldHelper
) {}
public function prepare(Entity $entity, string $name): ?string
{
if (!$entity->has($name)) {
return null;
}
$value = $entity->get($name);
$fieldData = $this->fieldHelper->getData($entity->getEntityType(), $name);
if (!$fieldData) {
return $value;
}
$entityType = $fieldData->getEntityType();
$field = $fieldData->getField();
$translation = $this->ormDefs
->getEntity($entityType)
->getField($field)
->getParam('translation');
if (!$translation) {
return $this->language->translateOption($value, $field, $entityType);
}
$map = $this->language->get($translation);
if (!is_array($map)) {
return $value;
}
return $map[$value] ?? $value;
}
}

View File

@@ -0,0 +1,41 @@
<?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\Tools\Export\Format\Xlsx\CellValuePreparators;
use Espo\ORM\Entity;
use Espo\Tools\Export\Format\CellValuePreparator;
class Floating implements CellValuePreparator
{
public function prepare(Entity $entity, string $name): float
{
return $entity->get($name) ?? 0.0;
}
}

View 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\Tools\Export\Format\Xlsx\CellValuePreparators;
use Espo\ORM\Entity;
use Espo\Tools\Export\Format\CellValuePreparator;
class General implements CellValuePreparator
{
public function prepare(Entity $entity, string $name): string|bool|int|float|null
{
$value = $entity->get($name);
if ($value === null) {
return null;
}
if (
!is_string($value) &&
!is_int($value) &&
!is_float($value) &&
!is_bool($value)
) {
return null;
}
return $value;
}
}

View File

@@ -0,0 +1,42 @@
<?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\Tools\Export\Format\Xlsx\CellValuePreparators;
use Espo\ORM\Entity;
use Espo\Tools\Export\Format\CellValuePreparator;
class Integer implements CellValuePreparator
{
public function prepare(Entity $entity, string $name): int
{
/** @var int */
return $entity->get($name) ?? 0;
}
}

View File

@@ -0,0 +1,42 @@
<?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\Tools\Export\Format\Xlsx\CellValuePreparators;
use Espo\ORM\Entity;
use Espo\Tools\Export\Format\CellValuePreparator;
class Link implements CellValuePreparator
{
public function prepare(Entity $entity, string $name): ?string
{
/** @var ?string */
return $entity->get($name . 'Name');
}
}

View 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\Tools\Export\Format\Xlsx\CellValuePreparators;
use Espo\ORM\Entity;
use Espo\Tools\Export\Format\CellValuePreparator;
use stdClass;
class LinkMultiple implements CellValuePreparator
{
public function prepare(Entity $entity, string $name): ?string
{
if (
!$entity->has($name . 'Ids') ||
!$entity->has($name . 'Names')
) {
return null;
}
/** @var string[] $ids */
$ids = $entity->get($name . 'Ids');
/** @var ?stdClass $names */
$names = $entity->get($name . 'Names');
$nameList = array_map(function ($id) use ($names) {
return $names->$id ?? $id;
}, $ids);
return implode(',', $nameList);
}
}

View File

@@ -0,0 +1,108 @@
<?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\Tools\Export\Format\Xlsx\CellValuePreparators;
use Espo\Core\Utils\Language;
use Espo\ORM\Defs;
use Espo\ORM\Entity;
use Espo\Tools\Export\Format\CellValuePreparator;
use Espo\Tools\Export\Format\Xlsx\FieldHelper;
class MultiEnum implements CellValuePreparator
{
public function __construct(
private Defs $ormDefs,
private Language $language,
private FieldHelper $fieldHelper
) {}
public function prepare(Entity $entity, string $name): ?string
{
if (!$entity->has($name)) {
return null;
}
$list = $entity->get($name);
if (!is_array($list)) {
return null;
}
/** @var string[] $list */
$fieldData = $this->fieldHelper->getData($entity->getEntityType(), $name);
if (!$fieldData) {
return $this->joinList($list);
}
$entityType = $fieldData->getEntityType();
$field = $fieldData->getField();
$translation = $this->ormDefs
->getEntity($entityType)
->getField($field)
->getParam('translation');
if (!$translation) {
return $this->joinList(
array_map(
function ($item) use ($field, $entityType) {
return $this->language->translateOption($item, $field, $entityType);
},
$list
)
);
}
$map = $this->language->get($translation);
if (!is_array($map)) {
return $this->joinList($list);
}
return $this->joinList(
array_map(
function ($item) use ($map) {
return $map[$item] ?? $item;
},
$list
)
);
}
/**
* @param string[] $list
*/
private function joinList(array $list): string
{
return implode(', ', $list);
}
}

View File

@@ -0,0 +1,60 @@
<?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\Tools\Export\Format\Xlsx\CellValuePreparators;
use Espo\ORM\Entity;
use Espo\Tools\Export\Format\CellValuePreparator;
class PersonName implements CellValuePreparator
{
public function prepare(Entity $entity, string $name): ?string
{
$value = $entity->get($name);
if ($value) {
return $value;
}
$arr = [];
$firstName = $entity->get('first' . ucfirst($name));
$lastName = $entity->get('last' . ucfirst($name));
if ($firstName) {
$arr[] = $firstName;
}
if ($lastName) {
$arr[] = $lastName;
}
return implode(' ', $arr) ?: null;
}
}

View File

@@ -0,0 +1,60 @@
<?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\Tools\Export\Format\Xlsx;
class FieldData
{
public function __construct(
private string $entityType,
private string $field,
private string $type,
private ?string $link
) {}
public function getEntityType(): string
{
return $this->entityType;
}
public function getField(): string
{
return $this->field;
}
public function getType(): string
{
return $this->type;
}
public function getLink(): ?string
{
return $this->link;
}
}

View File

@@ -0,0 +1,115 @@
<?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\Tools\Export\Format\Xlsx;
use Espo\Core\ORM\Type\FieldType;
use Espo\ORM\Defs;
class FieldHelper
{
public function __construct(
private Defs $ormDefs
) {}
public function isForeignReference(string $name): bool
{
return str_contains($name, '_');
}
private function isForeign(string $entityType, string $name): bool
{
if ($this->isForeignReference($name)) {
return true;
}
$entityDefs = $this->ormDefs->getEntity($entityType);
return
$entityDefs->hasField($name) &&
$entityDefs->getField($name)->getType() === FieldType::FOREIGN;
}
public function getData(string $entityType, string $name): ?FieldData
{
$entityDefs = $this->ormDefs->getEntity($entityType);
if (!$this->isForeign($entityType, $name)) {
if (!$entityDefs->hasField($name)) {
return null;
}
$type = $entityDefs
->getField($name)
->getType();
return new FieldData($entityType, $name, $type, null);
}
$link = null;
$field = null;
if (
$entityDefs->hasField($name) &&
$entityDefs->getField($name)->getType() === FieldType::FOREIGN
) {
$fieldDefs = $entityDefs->getField($name);
$link = $fieldDefs->getParam('link');
$field = $fieldDefs->getParam('field');
} else if (str_contains($name, '_')) {
[$link, $field] = explode('_', $name);
}
if (!$link || !$field) {
return null;
}
$entityDefs = $this->ormDefs->getEntity($entityType);
if (!$entityDefs->hasRelation($link)) {
return null;
}
$relationDefs = $entityDefs->getRelation($link);
if (!$relationDefs->hasForeignEntityType()) {
return null;
}
$foreignEntityType = $relationDefs->getForeignEntityType();
$type = $this->ormDefs
->getEntity($foreignEntityType)
->getField($field)
->getType();
return new FieldData($foreignEntityType, $field, $type, $link);
}
}

View File

@@ -0,0 +1,296 @@
<?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\Tools\Export\Format\Xlsx;
use Espo\Core\Field\Currency;
use Espo\Core\Field\Date;
use Espo\Core\Field\DateTime;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\DateTime as DateTimeUtil;
use Espo\Core\Utils\Language;
use Espo\Core\Utils\Metadata;
use Espo\ORM\Entity;
use Espo\Tools\Export\Collection;
use Espo\Tools\Export\Format\CellValuePreparator;
use Espo\Tools\Export\Format\CellValuePreparatorFactory;
use Espo\Tools\Export\Processor as ProcessorInterface;
use Espo\Tools\Export\Processor\Params;
use GuzzleHttp\Psr7\Stream;
use LogicException;
use OpenSpout\Common\Entity\Cell;
use OpenSpout\Common\Entity\Style\Style;
use OpenSpout\Common\Exception\InvalidArgumentException;
use OpenSpout\Common\Exception\IOException;
use OpenSpout\Writer\Exception\WriterNotOpenedException;
use OpenSpout\Writer\XLSX\Writer;
use OpenSpout\Writer\XLSX\Entity\SheetView;
use OpenSpout\Writer\XLSX\Options;
use OpenSpout\Common\Entity\Row;
use Psr\Http\Message\StreamInterface;
use RuntimeException;
class OpenSpoutProcessor implements ProcessorInterface
{
private const FORMAT = 'xlsx';
/** @var array<string, CellValuePreparator> */
private array $preparatorsCache = [];
/** @var array<string, string> */
private array $typesCache = [];
public function __construct(
private FieldHelper $fieldHelper,
private CellValuePreparatorFactory $cellValuePreparatorFactory,
private Language $language,
private DateTimeUtil $dateTime,
private Config $config,
private Metadata $metadata
) {}
/**
* @throws IOException
* @throws WriterNotOpenedException
* @throws InvalidArgumentException
*/
public function process(Params $params, Collection $collection): StreamInterface
{
if (!$params->getFieldList()) {
throw new LogicException("No field list.");
}
$filePath = tempnam(sys_get_temp_dir(), 'espo-export');
if (!$filePath) {
throw new RuntimeException("Could not create a temp file.");
}
$options = new Options();
$options->setColumnWidthForRange(20, 1, count($params->getFieldList()));
$writer = new Writer($options);
$writer->openToFile($filePath);
$sheetView = new SheetView();
$sheetView->setFreezeRow(2);
$writer->getCurrentSheet()->setSheetView($sheetView);
$headerCells = [];
foreach ($params->getFieldList() as $name) {
$label = $this->translateLabel($params->getEntityType(), $name);
$headerCells[] = Cell::fromValue($label, (new Style())->setFontBold());
}
$writer->addRow(new Row($headerCells));
foreach ($collection as $entity) {
$this->processRow($params, $entity, $writer);
}
$writer->close();
$resource = fopen($filePath, 'r+');
if ($resource === false) {
throw new RuntimeException("Could not open temp.");
}
$stream = new Stream($resource);
$stream->seek(0);
return $stream;
}
private function translateLabel(string $entityType, string $name): string
{
$label = $name;
$fieldData = $this->fieldHelper->getData($entityType, $name);
$isForeignReference = $this->fieldHelper->isForeignReference($name);
if ($isForeignReference && $fieldData && $fieldData->getLink()) {
$label =
$this->language->translateLabel($fieldData->getLink(), 'links', $entityType) . '.' .
$this->language->translateLabel($fieldData->getField(), 'fields', $fieldData->getEntityType());
}
if (!$isForeignReference) {
$label = $this->language->translateLabel($name, 'fields', $entityType);
}
return $label;
}
private function processRow(Params $params, Entity $entity, Writer $writer): void
{
$cells = [];
foreach ($params->getFieldList() ?? [] as $name) {
$cells[] = $this->prepareCell($params, $entity, $name);
}
$writer->addRow(new Row($cells));
}
private function prepareCell(Params $params, Entity $entity, mixed $name): Cell
{
$type = $this->getFieldType($params->getEntityType(), $name);
$value = $this->getPreparator($type)
->prepare($entity, $name);
if (is_string($value)) {
$value = $this->sanitizeCellValue($value);
return Cell\StringCell::fromValue($value);
}
if (is_int($value)) {
return Cell\NumericCell::fromValue($value);
}
if (is_float($value)) {
return Cell\NumericCell::fromValue($value);
}
if (is_bool($value)) {
return Cell\BooleanCell::fromValue($value);
}
if ($value instanceof Date) {
$dateFormat = self::convertDateFormat($this->dateTime->getDateFormat());
$style = new Style();
$style->setFormat($dateFormat);
return Cell\DateTimeCell::fromValue($value->toDateTime(), $style);
}
if ($value instanceof DateTime) {
$dateTimeFormat = self::convertDateFormat($this->dateTime->getDateTimeFormat());
$style = new Style();
$style->setFormat($dateTimeFormat);
return Cell\DateTimeCell::fromValue($value->toDateTime(), $style);
}
if ($value instanceof Currency) {
$format = $this->getCurrencyFormat($value->getCode());
$style = new Style();
$style->setFormat($format);
return Cell\NumericCell::fromValue($value->getAmount(), $style);
}
return Cell::fromValue('');
}
private function getFieldType(string $entityType, string $name): string
{
$key = $entityType . '-' . $name;
$type = $this->typesCache[$key] ?? null;
if (!$type) {
$fieldData = $this->fieldHelper->getData($entityType, $name);
$type = $fieldData ? $fieldData->getType() : 'base';
$this->typesCache[$key] = $type;
}
return $type;
}
private function getPreparator(string $type): CellValuePreparator
{
if (!array_key_exists($type, $this->preparatorsCache)) {
$this->preparatorsCache[$type] = $this->cellValuePreparatorFactory->create(self::FORMAT, $type);
}
return $this->preparatorsCache[$type];
}
private static function convertDateFormat(string $format): string
{
$map = [
'MM' => 'mm',
'DD' => 'dd',
'YYYY' => 'yyyy',
'HH' => 'hh',
'mm' => 'mm',
'hh' => 'hh',
'A' => 'AM/PM',
'a' => 'AM/PM',
'ss' => 'ss',
];
return str_replace(
array_keys($map),
array_values($map),
$format
);
}
private function getCurrencyFormat(string $code): string
{
$currencySymbol = $this->metadata->get(['app', 'currency', 'symbolMap', $code], '');
$currencyFormat = $this->config->get('currencyFormat') ?? 2;
if ($currencyFormat === 3) {
return '#,##0.00_-"' . $currencySymbol . '"';
}
return '[$' . $currencySymbol . '-409]#,##0.00;-[$' . $currencySymbol . '-409]#,##0.00';
}
private function sanitizeCellValue(string $value): string
{
if ($value === '') {
return $value;
}
if (is_numeric($value)) {
return $value;
}
if (in_array($value[0], ['+', '-', '@', '='])) {
return "'" . $value;
}
return $value;
}
}

View File

@@ -0,0 +1,134 @@
<?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\Tools\Export\Format\Xlsx;
use Espo\Core\ORM\Type\FieldType;
use Espo\Core\Utils\Metadata;
use Espo\ORM\Defs\Params\RelationParam;
use Espo\ORM\Entity;
use Espo\Tools\Export\Params;
use Espo\Tools\Export\Processor;
use Espo\Tools\Export\ProcessorParamsHandler;
class ParamsHandler implements ProcessorParamsHandler
{
public function __construct(
private Metadata $metadata
) {}
public function handle(Params $params, Processor\Params $processorParams): Processor\Params
{
$fieldList = $processorParams->getFieldList();
if ($fieldList === null) {
return $processorParams;
}
$fieldList = $this->filterFieldList($params->getEntityType(), $fieldList, $params->allFields());
$attributeList = $processorParams->getAttributeList();
$this->addAdditionalAttributes($params->getEntityType(), $attributeList, $fieldList);
return $processorParams
->withAttributeList($attributeList)
->withFieldList($fieldList);
}
/**
* @param string[] $fieldList
* @return string[]
*/
private function filterFieldList(string $entityType, array $fieldList, bool $exportAllFields): array
{
if ($exportAllFields) {
foreach ($fieldList as $i => $field) {
$type = $this->metadata->get(['entityDefs', $entityType, 'fields', $field, 'type']);
if (in_array($type, [FieldType::LINK_MULTIPLE, FieldType::ATTACHMENT_MULTIPLE])) {
unset($fieldList[$i]);
}
}
}
return array_values($fieldList);
}
/**
* @param string[] $attributeList
* @param string[] $fieldList
*/
private function addAdditionalAttributes(string $entityType, array &$attributeList, array $fieldList): void
{
$linkList = [];
if (!in_array('id', $attributeList)) {
$attributeList[] = 'id';
}
$linkDefs = $this->metadata->get(['entityDefs', $entityType, 'links']) ?? [];
foreach ($linkDefs as $link => $defs) {
$linkType = $defs['type'] ?? null;
if (!$linkType) {
continue;
}
if ($linkType === Entity::BELONGS_TO_PARENT) {
$linkList[] = $link;
continue;
}
if ($linkType === Entity::BELONGS_TO && !empty($defs[RelationParam::NO_JOIN])) {
if ($this->metadata->get(['entityDefs', $entityType, 'fields', $link])) {
$linkList[] = $link;
}
}
}
foreach ($linkList as $item) {
if (in_array($item, $fieldList) && !in_array($item . 'Name', $attributeList)) {
$attributeList[] = $item . 'Name';
}
}
foreach ($fieldList as $field) {
$type = $this->metadata->get(['entityDefs', $entityType, 'fields', $field, 'type']);
if ($type === FieldType::CURRENCY_CONVERTED) {
if (!in_array($field, $attributeList)) {
$attributeList[] = $field;
}
}
}
}
}

View File

@@ -0,0 +1,657 @@
<?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\Tools\Export\Format\Xlsx;
use Espo\Core\Field\Currency;
use Espo\Core\Field\Date;
use Espo\Core\Field\DateTime as DateTimeValue;
use Espo\Core\ORM\Type\FieldType;
use Espo\Core\Utils\Config\ApplicationConfig;
use Espo\Entities\Attachment;
use Espo\Core\FileStorage\Manager as FileStorageManager;
use Espo\Core\ORM\EntityManager;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\DateTime as DateTimeUtil;
use Espo\Core\Utils\Language;
use Espo\Core\Utils\Metadata;
use Espo\ORM\Defs\Params\RelationParam;
use Espo\ORM\Entity;
use Espo\Tools\Export\Collection;
use Espo\Tools\Export\Format\CellValuePreparator;
use Espo\Tools\Export\Format\CellValuePreparatorFactory;
use Espo\Tools\Export\Processor as ProcessorInterface;
use Espo\Tools\Export\Processor\Params;
use Psr\Http\Message\StreamInterface;
use GuzzleHttp\Psr7\Stream;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDate;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
use DateTime;
use DateTimeZone;
use RuntimeException;
class PhpSpreadsheetProcessor implements ProcessorInterface
{
private const FORMAT = 'xlsx';
private const PARAM_RECORD_LINKS = 'recordLinks';
private const PARAM_TITLE = 'title';
/** @var array<string, CellValuePreparator> */
private array $preparatorsCache = [];
/** @var array<string, mixed> */
private array $titleStyle = [
'font' => [
'bold' => true,
'size' => 12,
]
];
/** @var array<string, mixed> */
private array $dateStyle = [
'font' => [
'size' => 12,
]
];
/** @var array<string, mixed> */
private array $headerStyle = [
'font' => [
'bold' => true,
'size' => 12,
]
];
/** @var array<string, mixed> */
private array $linkStyle = [
'font' => [
'color' => ['rgb' => '345b7c'],
'underline' => 'single',
]
];
public function __construct(
private Config $config,
private Metadata $metadata,
private Language $language,
private DateTimeUtil $dateTime,
private EntityManager $entityManager,
private FileStorageManager $fileStorageManager,
private FieldHelper $fieldHelper,
private CellValuePreparatorFactory $cellValuePreparatorFactory,
private ApplicationConfig $applicationConfig,
) {}
/**
* @throws SpreadsheetException
* @throws WriterException
*/
public function process(Params $params, Collection $collection): StreamInterface
{
$entityType = $params->getEntityType();
$fieldList = $params->getFieldList();
if ($fieldList === null) {
throw new RuntimeException("Field list is required.");
}
$sheetName = $this->getSheetNameFromParams($params);
$exportName = $params->getName() ??
$this->language->translate($entityType, 'scopeNamesPlural');
$phpExcel = new Spreadsheet();
$headerRowNumber = $params->getParam(self::PARAM_TITLE) ? 3 : 1;
$sheet = $phpExcel->setActiveSheetIndex(0)
->setTitle($sheetName)
->freezePane('A' . ($headerRowNumber + 1));
$now = new DateTime();
$now->setTimezone(new DateTimeZone($this->config->get('timeZone', 'UTC')));
if ($params->getParam(self::PARAM_TITLE)) {
$sheet
->setCellValue('A1', $this->sanitizeCellValue($exportName))
->setCellValue('A2',
SharedDate::PHPToExcel(strtotime($now->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT)))
);
$sheet->getStyle('A1')->applyFromArray($this->titleStyle);
$sheet->getStyle('A2')->applyFromArray($this->dateStyle);
$sheet->getStyle('A2')
->getNumberFormat()
->setFormatCode($this->dateTime->getDateTimeFormat());
}
$azRange = $this->getColumnsRange($fieldList);
$rowNumber = $headerRowNumber;
$linkColList = [];
$lastIndex = 0;
foreach ($fieldList as $i => $name) {
$col = $azRange[$i];
$type = 'base';
$label = $this->translateLabel($entityType, $name);
$fieldData = $this->fieldHelper->getData($entityType, $name);
if ($fieldData) {
$type = $fieldData->getType();
}
$sheet->setCellValue($col . $rowNumber, $this->sanitizeCellValue($label));
$sheet->getColumnDimension($col)->setAutoSize(true);
$linkTypeList = $params->getParam(self::PARAM_RECORD_LINKS) ?
[FieldType::URL, FieldType::PHONE, FieldType::EMAIL, FieldType::LINK, FieldType::LINK_PARENT] :
['url'];
if (
in_array($type, $linkTypeList) ||
$params->getParam(self::PARAM_RECORD_LINKS) && $name === 'name'
) {
$linkColList[] = $col;
}
$lastIndex = $i;
}
$col = $azRange[$lastIndex];
$sheet->getStyle("A$rowNumber:$col$rowNumber")->applyFromArray($this->headerStyle);
$sheet->setAutoFilter("A$rowNumber:$col$rowNumber");
$typesCache = [];
$rowNumber++;
foreach ($collection as $entity) {
$this->processRow(
$entity,
$sheet,
$rowNumber,
$fieldList,
$azRange,
$typesCache
);
$rowNumber++;
}
$sheet->getStyle("A$headerRowNumber:A$rowNumber")
->getNumberFormat()
->setFormatCode(NumberFormat::FORMAT_TEXT);
$startingRowNumber = 2;
if ($params->getParam(self::PARAM_TITLE)) {
$startingRowNumber += 2;
}
foreach ($fieldList as $i => $name) {
$col = $azRange[$i];
if (!array_key_exists($name, $typesCache)) {
break;
}
$type = $typesCache[$name];
$coordinate = "$col$startingRowNumber:$col$rowNumber";
switch ($type) {
case FieldType::CURRENCY:
case FieldType::CURRENCY_CONVERTED:
break;
case FieldType::INT:
$sheet->getStyle($coordinate)
->getNumberFormat()
->setFormatCode('0');
break;
case FieldType::FLOAT:
$sheet->getStyle($coordinate)
->getNumberFormat()
->setFormatCode(NumberFormat::FORMAT_NUMBER_COMMA_SEPARATED1);
break;
case FieldType::DATE:
$sheet->getStyle($coordinate)
->getNumberFormat()
->setFormatCode($this->dateTime->getDateFormat());
break;
case FieldType::DATETIME_OPTIONAL:
case FieldType::DATETIME:
$sheet->getStyle($coordinate)
->getNumberFormat()
->setFormatCode($this->dateTime->getDateTimeFormat());
break;
default:
$sheet->getStyle($coordinate)
->getNumberFormat()
->setFormatCode('@');
break;
}
}
foreach ($linkColList as $linkColumn) {
$sheet
->getStyle($linkColumn . $startingRowNumber . ':' . $linkColumn . $rowNumber)
->applyFromArray($this->linkStyle);
}
$objWriter = IOFactory::createWriter($phpExcel, 'Xlsx');
$resource = fopen('php://temp', 'r+');
if ($resource === false) {
throw new RuntimeException("Could not open temp.");
}
$objWriter->save($resource);
$stream = new Stream($resource);
$stream->seek(0);
return $stream;
}
private function translateLabel(string $entityType, string $name): string
{
$label = $name;
$fieldData = $this->fieldHelper->getData($entityType, $name);
$isForeignReference = $this->fieldHelper->isForeignReference($name);
if ($isForeignReference && $fieldData && $fieldData->getLink()) {
$label =
$this->language->translateLabel($fieldData->getLink(), 'links', $entityType) . '.' .
$this->language->translateLabel($fieldData->getField(), 'fields', $fieldData->getEntityType());
}
if (!$isForeignReference) {
$label = $this->language->translateLabel($name, 'fields', $entityType);
}
return $label;
}
/**
* @param string[] $fieldList
* @return string[]
*/
private function getColumnsRange(array $fieldList): array
{
$azRange = range('A', 'Z');
$azRangeCopied = $azRange;
foreach ($azRangeCopied as $i => $char1) {
foreach ($azRangeCopied as $j => $char2) {
$azRange[] = $char1 . $char2;
if ($i * count($azRangeCopied) + $j === count($fieldList)) {
break 2;
}
}
}
return $azRange;
}
/**
* @param string[] $fieldList
* @param string[] $azRange
* @param array<string, string> $typesCache
* @throws SpreadsheetException
*/
private function processRow(
Entity $entity,
Worksheet $sheet,
int $rowNumber,
array $fieldList,
array $azRange,
array &$typesCache
): void {
foreach ($fieldList as $i => $name) {
$col = $azRange[$i];
$coordinate = $col . $rowNumber;
$this->processCell(
$entity,
$sheet,
$rowNumber,
$coordinate,
$name,
$typesCache
);
}
}
/**
* @param array<string, string> $typesCache
* @throws SpreadsheetException
*/
private function processCell(
Entity $entity,
Worksheet $sheet,
int $rowNumber,
string $coordinate,
string $name,
array &$typesCache
): void {
$entityType = $entity->getEntityType();
$type = $typesCache[$name] ?? null;
if (!$type) {
$fieldData = $this->fieldHelper->getData($entityType, $name);
$type = $fieldData ? $fieldData->getType() : 'base';
$typesCache[$name] = $type;
}
$preparator = $this->getPreparator($type);
$value = $preparator->prepare($entity, $name);
if ($type === FieldType::IMAGE) {
$this->applyImage(
$entity,
$coordinate,
$sheet,
$rowNumber,
$name
);
$value = null;
}
$value = $this->sanitizeCellValue($value);
if (is_string($value)) {
$sheet->setCellValueExplicit($coordinate, $value, DataType::TYPE_STRING);
} else if (is_int($value) || is_float($value)) {
$sheet->setCellValueExplicit($coordinate, $value, DataType::TYPE_NUMERIC);
}
if (is_bool($value)) {
$sheet->setCellValueExplicit($coordinate, $value, DataType::TYPE_BOOL);
} else if ($value instanceof Date) {
$sheet->setCellValue(
$coordinate,
SharedDate::PHPToExcel(
strtotime($value->toString())
)
);
} else if ($value instanceof DateTimeValue) {
$sheet->setCellValue(
$coordinate,
SharedDate::PHPToExcel(
strtotime($value->toDateTime()->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT))
)
);
} else if ($value instanceof Currency) {
$sheet->setCellValue($coordinate, $value->getAmount());
$sheet->getStyle($coordinate)
->getNumberFormat()
->setFormatCode($this->getCurrencyFormatCode($value->getCode()));
}
$this->applyLinks(
$type,
$entity,
$sheet,
$coordinate,
$name
);
}
/**
* @throws SpreadsheetException
*/
private function applyImage(
Entity $entity,
string $coordinate,
Worksheet $sheet,
int $rowNumber,
string $name
): void {
$attachmentId = $entity->get($name . 'Id');
if (!$attachmentId) {
return;
}
/** @var ?Attachment $attachment */
$attachment = $this->entityManager->getEntityById(Attachment::ENTITY_TYPE, $attachmentId);
if (!$attachment) {
return;
}
$objDrawing = new Drawing();
$filePath = $this->fileStorageManager->getLocalFilePath($attachment);
if (!$filePath || !file_exists($filePath)) {
return;
}
$objDrawing->setPath($filePath);
$objDrawing->setHeight(100);
$objDrawing->setCoordinates($coordinate);
$objDrawing->setWorksheet($sheet);
$sheet->getRowDimension($rowNumber)->setRowHeight(100);
}
/**
* @throws SpreadsheetException
*/
private function applyLinks(
string $type,
Entity $entity,
Worksheet $sheet,
string $coordinate,
string $name
): void {
$entityType = $entity->getEntityType();
$link = null;
$foreignLink = null;
$foreignField = null;
if (strpos($name, '_')) {
[$foreignLink, $foreignField] = explode('_', $name);
}
$siteUrl = $this->applicationConfig->getSiteUrl();
if ($name === 'name') {
if ($entity->hasId()) {
$link = "$siteUrl/#$entityType/view/{$entity->getId()}";
}
} else if ($type === FieldType::URL) {
$value = $entity->get($name);
if ($value) {
$link = $this->sanitizeUrl($value);
}
} else if ($type === FieldType::LINK) {
$idValue = $entity->get($name . 'Id');
if ($idValue && $foreignField) {
if (!$foreignLink) {
$foreignEntity =
$this->metadata->get(['entityDefs', $entityType, 'links', $name, RelationParam::ENTITY]);
} else {
$foreignEntity1 = $this->metadata
->get(['entityDefs', $entityType, 'links', $foreignLink, 'entity']);
$foreignEntity = $this->metadata
->get(['entityDefs', $foreignEntity1, 'links', $foreignField, 'entity']);
}
if ($foreignEntity) {
$link = "$siteUrl/#$foreignEntity/view/$idValue";
}
}
} else if ($type === FieldType::FILE) {
$idValue = $entity->get($name . 'Id');
if ($idValue) {
$link = "$siteUrl/?entryPoint=download&id=$idValue";
}
} else if ($type === FieldType::LINK_PARENT) {
$idValue = $entity->get($name . 'Id');
$typeValue = $entity->get($name . 'Type');
if ($idValue && $typeValue) {
$link = "$siteUrl/#$typeValue/view/$idValue";
}
} else if ($type === FieldType::PHONE) {
$value = $entity->get($name);
if ($value) {
$link = "tel:$value";
}
} else if ($type === FieldType::EMAIL) {
$value = $entity->get($name);
if ($value) {
$link = "mailto:$value";
}
}
if (!$link) {
return;
}
$cell = $sheet->getCell($coordinate);
$hyperLink = $cell->getHyperlink();
$hyperLink->setUrl($link);
$hyperLink->setTooltip($link);
}
private function getPreparator(string $type): CellValuePreparator
{
if (!array_key_exists($type, $this->preparatorsCache)) {
$this->preparatorsCache[$type] = $this->cellValuePreparatorFactory->create(self::FORMAT, $type);
}
return $this->preparatorsCache[$type];
}
private function getCurrencyFormatCode(string $currency): string
{
$currencySymbol = $this->metadata->get(['app', 'currency', 'symbolMap', $currency], '');
$currencyFormat = $this->config->get('currencyFormat') ?? 2;
if ($currencyFormat == 3) {
return '#,##0.00_-"' . $currencySymbol . '"';
}
return '[$'.$currencySymbol.'-409]#,##0.00;-[$'.$currencySymbol.'-409]#,##0.00';
}
private function getSheetNameFromParams(Params $params): string
{
$exportName =
$params->getName() ??
$this->language->translateLabel($params->getEntityType(), 'scopeNamesPlural');
$badCharList = ['*', ':', '/', '\\', '?', '[', ']'];
$sheetName = mb_substr($exportName, 0, 30, 'utf-8');
$sheetName = str_replace($badCharList, ' ', $sheetName);
return str_replace('\'', '', $sheetName);
}
private function sanitizeCellValue(mixed $value): mixed
{
if (!is_string($value)) {
return $value;
}
if ($value === '') {
return $value;
}
if (is_numeric($value)) {
return $value;
}
if (in_array($value[0], ['+', '-', '@', '='])) {
return "'" . $value;
}
return $value;
}
private function sanitizeUrl(string $value): ?string
{
$link = $value;
if (!preg_match("/[a-z]+:\/\//", $link)) {
$link = 'https://' . $link;
}
if (filter_var($link, FILTER_VALIDATE_URL)) {
return $link;
}
return null;
}
}

View File

@@ -0,0 +1,83 @@
<?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\Tools\Export\Format\Xlsx;
use Espo\Core\Exceptions\Error;
use Espo\Tools\Export\Collection;
use Espo\Tools\Export\Processor as ProcessorInterface;
use Espo\Tools\Export\Processor\Params;
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
use Psr\Http\Message\StreamInterface;
class Processor implements ProcessorInterface
{
private const PARAM_LITE = 'lite';
public function __construct(
private PhpSpreadsheetProcessor $phpSpreadsheetProcessor,
private OpenSpoutProcessor $openSpoutProcessor,
) {}
/**
* @throws Error
*/
public function process(Params $params, Collection $collection): StreamInterface
{
return $params->getParam(self::PARAM_LITE) ?
$this->processOpenSpout($params, $collection) :
$this->processPhpSpreadsheet($params, $collection);
}
/**
* @throws Error
*/
private function processPhpSpreadsheet(Params $params, Collection $collection): StreamInterface
{
try {
return $this->phpSpreadsheetProcessor->process($params, $collection);
} catch (SpreadsheetException|WriterException $e) {
throw new Error($e->getMessage());
}
}
/**
* @throws Error
*/
private function processOpenSpout(Params $params, Collection $collection): StreamInterface
{
try {
return $this->openSpoutProcessor->process($params, $collection);
} catch (\Throwable $e) {
throw new Error($e->getMessage());
}
}
}