snapshot of https://github.com/espocrm/espocrm.git (master, 2026-09-18; blobs > 131072B omitted)
Some checks failed
Test / Test on PHP 8.3 (push) Failing after 5s
Test / Test on PHP 8.4 (push) Failing after 4s
Test / Test on PHP 8.5 (push) Failing after 3s
TypeScript static analysis / TypeScript check (push) Failing after 14s
Test Integration on MySQL / Test on PHP 8.3 (push) Failing after 1s
Test Integration on MySQL / Test on PHP 8.5 (push) Failing after 1s
Test Integration on PostgreSQL / Test on PHP 8.4 fix (push) Failing after 1s
Test Integration on PostgreSQL / Test on PHP 8.4 master (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-18 04:19:15 +05:00
commit 7cc7e6b4f3
10077 changed files with 900756 additions and 0 deletions

View File

@@ -0,0 +1,128 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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\Classes\ConsoleCommands;
use Espo\Core\Authentication\ConfigDataProvider as AuthenticationConfig;
use Espo\Core\Console\Command;
use Espo\Core\Console\Command\Params;
use Espo\Core\Console\IO;
use Espo\Core\Upgrades\Migration\VersionDataProvider;
use Espo\Core\Utils\Config\SystemConfig;
use Espo\Core\Utils\Database\Helper;
use Exception;
/**
* @noinspection PhpUnused
*/
class AppCheck implements Command
{
public function __construct(
private Helper $databaseHelper,
private SystemConfig $systemConfig,
private VersionDataProvider $versionDataProvider,
private AuthenticationConfig $authenticationConfig,
) {}
public function run(Params $params, IO $io): void
{
$this->versionMatch($io);
$this->checkDb($io);
$this->maintenanceIsOff($io);
$this->cronEnabled($io);
}
private function versionMatch(IO $io): void
{
$io->write('Migration not needed: ');
if ($this->systemConfig->getVersion() === $this->versionDataProvider->getTargetVersion()) {
$this->writeOK($io);
} else {
$this->writeFail($io);
$io->setExitStatus(1);
}
$io->writeLine('');
}
private function checkDb(IO $io): void
{
$io->write('Database: ');
try {
$this->databaseHelper->createPDO();
$this->writeOK($io);
} catch (Exception) {
$this->writeFail($io);
$io->setExitStatus(1);
}
$io->writeLine('');
}
private function maintenanceIsOff(IO $io): void
{
$io->write('Not in maintenance mode: ');
if (!$this->authenticationConfig->isMaintenanceMode()) {
$this->writeOK($io);
} else {
$this->writeFail($io);
$io->setExitStatus(1);
}
$io->writeLine('');
}
private function cronEnabled(IO $io): void
{
$io->write('Cron is enabled: ');
if ($this->systemConfig->isCronEnabled()) {
$this->writeOK($io);
} else {
$this->writeFail($io);
$io->setExitStatus(1);
}
$io->writeLine('');
}
private function writeOK(IO $io): void
{
$io->write("\033[32mOK\033[0m");
}
private function writeFail(IO $io): void
{
$io->write("\033[31mFAIL\033[0m");
}
}

View File

@@ -0,0 +1,70 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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\Classes\ConsoleCommands;
use Espo\Core\Console\Command;
use Espo\Core\Console\Command\Params;
use Espo\Core\Console\IO;
use Espo\Core\Utils\File\Manager as FileManager;
use Espo\Core\Utils\System;
use Espo\Core\Utils\Util;
/**
* @noinspection PhpUnused
*/
class CheckFilePermissions implements Command
{
public function __construct(
private FileManager $fileManager,
private System $system
) {}
public function run(Params $params, IO $io): void
{
$io->writeLine("\nNote: Run this command under the web server user.\n");
$io->writeLine('Writable:');
$io->writeLine('');
foreach ($this->fileManager->getPermissionUtils()->getWritableList() as $path) {
$fullPath = Util::concatPath($this->system->getRootDir(), $path);
$isWritable = $this->fileManager->isWritable($fullPath);
$msg = " " . ($isWritable ? "OK" : "FAIL") . " : $path";
if (!$isWritable) {
$io->setExitStatus(1);
}
$io->writeLine($msg);
}
}
}

View File

@@ -0,0 +1,50 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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\Classes\ConsoleCommands;
use Espo\Core\Console\Command;
use Espo\Core\Console\Command\Params;
use Espo\Core\Console\IO;
use Espo\Core\Utils\Config\Populator;
/**
* @noinspection PhpUnused
*/
class ConfigPopulate implements Command
{
public function __construct(
private Populator $populator,
) {}
public function run(Params $params, IO $io): void
{
$this->populator->populate();
}
}

View File

@@ -0,0 +1,105 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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\Classes\ConsoleCommands;
use Espo\Core\Console\Command;
use Espo\Core\Console\Command\Params;
use Espo\Core\Console\IO;
use Espo\Core\Utils\Config;
use Espo\Entities\User;
use Espo\ORM\EntityManager;
use RuntimeException;
/**
* @noinspection PhpUnused
*/
class CreateAdminUser implements Command
{
public function __construct(
private EntityManager $entityManager,
private Config $config
) {}
public function run(Params $params, IO $io): void
{
$userName = $params->getArgument(0);
if (!$userName) {
$io->writeLine("A username must be specified as the first argument.");
$io->setExitStatus(1);
return;
}
/** @var ?string $regExp */
$regExp = $this->config->get('userNameRegularExpression');
if (!$regExp) {
throw new RuntimeException("No `userNameRegularExpression` in config.");
}
if (
str_contains($userName, ' ') ||
preg_replace("/{$regExp}/", '_', $userName) !== $userName
) {
$io->writeLine("Not allowed username.");
$io->setExitStatus(1);
return;
}
$repository = $this->entityManager->getRDBRepositoryByClass(User::class);
$existingUser = $repository
->where([User::FIELD_USER_NAME => $userName])
->findOne();
if ($existingUser) {
$io->writeLine("A user with the same username already exists.");
$io->setExitStatus(1);
return;
}
$user = $repository->getNew();
$user->setUserName($userName);
$user->setType(User::TYPE_ADMIN);
$user->setLastName(ucfirst($userName));
$repository->save($user);
$message = "The user '$userName' has been created. " .
"Set password with the command: `bin/command set-password $userName`.";
$io->writeLine($message);
}
}

View File

@@ -0,0 +1,70 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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\Classes\ConsoleCommands;
use Espo\Core\Console\Command;
use Espo\Core\Console\Command\Params;
use Espo\Core\Console\IO;
use Espo\Core\Job\Exceptions\TooFrequentRun;
use Espo\Core\Job\JobManager;
use Espo\Core\Job\PrepareProcessor;
use Espo\Core\Utils\Config\SystemConfig;
use RuntimeException;
/**
* @noinspection PhpUnused
*/
class CronRun implements Command
{
public function __construct(
private PrepareProcessor $prepareProcessor,
private JobManager $jobManager,
private SystemConfig $config,
) {}
public function run(Params $params, IO $io): void
{
if (!$this->config->isCronEnabled()) {
throw new RuntimeException("Cron cannot be run as 'cronDisabled' is set to true in the config.");
}
if (!$params->hasFlag('force') && $this->config->isMaintenanceMode()) {
throw new RuntimeException("Cron cannot be run in maintenance mode. You can use --force flag.");
}
try {
$this->prepareProcessor->process();
} catch (TooFrequentRun $e) {
throw new RuntimeException('Too frequent run.', previous: $e);
}
$this->jobManager->processMainQueue();
}
}

View File

@@ -0,0 +1,60 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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\Classes\ConsoleCommands;
use Espo\Core\Console\Command;
use Espo\Core\Console\Command\Params;
use Espo\Core\Console\IO;
use Espo\Core\Utils\Database\Helper;
use Exception;
/**
* @noinspection PhpUnused
*/
class DbCheck implements Command
{
public function __construct(
private Helper $helper,
) {}
public function run(Params $params, IO $io): void
{
try {
$this->helper->createPDO();
$io->writeLine('OK');
} catch (Exception) {
$io->writeErrorLine("Not connected");
$io->setExitStatus(1);
return;
}
}
}

View File

@@ -0,0 +1,93 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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\Classes\ConsoleCommands;
use Espo\Core\Console\Command;
use Espo\Core\Console\Command\Params;
use Espo\Core\Console\IO;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Json;
/**
* @noinspection PhpUnused
*/
class GetConfigParam implements Command
{
public function __construct(
private Config $config,
) {}
public function run(Params $params, IO $io): void
{
$param = $params->getArgument(0) ?? null;
if ($param === null) {
$io->writeLine("Parameter name is not specified.");
$io->setExitStatus(1);
return;
}
if (!$this->config->has($param)) {
$io->writeLine("Parameter '$param' is not set in config.");
$io->setExitStatus(1);
return;
}
$isJson = $params->hasFlag('json');
$value = $this->config->get($param);
$value = $this->formatValue($value, $isJson);
$io->writeLine($value);
}
private function formatValue(mixed $value, bool $isJson): string
{
if ($isJson) {
return self::toJson($value);
}
return match (true) {
$value === true => 'true',
$value === false => 'false',
$value === null => 'null',
is_scalar($value) => (string) $value,
default => self::toJson($value),
};
}
private static function toJson(mixed $value): string
{
return Json::encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
}
}

View File

@@ -0,0 +1,143 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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\Classes\ConsoleCommands;
use Espo\Tools\Import\Service;
use Espo\Core\Utils\File\Manager as FileManager;
use Espo\Core\Console\Command;
use Espo\Core\Console\Command\Params;
use Espo\Core\Console\IO;
use Throwable;
class Import implements Command
{
public function __construct(private Service $service, private FileManager $fileManager)
{}
public function run(Params $params, IO $io) : void
{
$id = $params->getOption('id');
$filePath = $params->getOption('file');
$paramsId = $params->getOption('paramsId');
$forceResume = $params->hasFlag('resume');
$revert = $params->hasFlag('revert');
if (!$id && $filePath) {
if (!$paramsId) {
$io->writeLine("You need to specify --params-id option.");
$io->setExitStatus(1);
return;
}
if (!$this->fileManager->isFile($filePath)) {
$io->writeLine("File not found.");
$io->setExitStatus(1);
return;
}
$contents = $this->fileManager->getContents($filePath);
try {
$result = $this->service->importContentsWithParamsId($contents, $paramsId);
$resultId = $result->getId();
$countCreated = $result->getCountCreated();
$countUpdated = $result->getCountUpdated();
$countError = $result->getCountError();
$countDuplicate = $result->getCountDuplicate();
} catch (Throwable $e) {
$io->writeLine("Error occurred: " . $e->getMessage());
$io->setExitStatus(1);
return;
}
$io->writeLine("Finished.");
$io->writeLine(" Import ID: {$resultId}");
$io->writeLine(" Created: {$countCreated}");
$io->writeLine(" Updated: {$countUpdated}");
$io->writeLine(" Duplicates: {$countDuplicate}");
$io->writeLine(" Errors: {$countError}");
return;
}
if ($id && $revert) {
$io->writeLine("Reverting import...");
try {
$this->service->revert($id);
} catch (Throwable $e) {
$io->writeLine("Error occurred: " . $e->getMessage());
$io->setExitStatus(1);
return;
}
$io->writeLine("Finished.");
return;
}
if ($id) {
$io->writeLine("Running import, this may take a while...");
try {
$result = $this->service->importById($id, true, $forceResume);
} catch (Throwable $e) {
$io->writeLine("Error occurred: " . $e->getMessage());
$io->setExitStatus(1);
return;
}
$countCreated = $result->getCountCreated();
$countUpdated = $result->getCountUpdated();
$countError = $result->getCountError();
$countDuplicate = $result->getCountDuplicate();
$io->writeLine("Finished.");
$io->writeLine(" Created: {$countCreated}");
$io->writeLine(" Updated: {$countUpdated}");
$io->writeLine(" Duplicates: {$countDuplicate}");
$io->writeLine(" Errors: {$countError}");
return;
}
$io->writeLine("Not enough params passed.");
$io->setExitStatus(1);
}
}

View File

@@ -0,0 +1,122 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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\Classes\ConsoleCommands;
use Espo\Core\Console\Command;
use Espo\Core\Console\Command\Params;
use Espo\Core\Console\Exceptions\InvalidArgument;
use Espo\Core\Console\IO;
use Espo\Core\Job\Processing\EnqueueDaemon;
use Espo\Core\Job\QueueName;
use RuntimeException;
/**
* @noinspection PhpUnused
*/
class JobEnqueue implements Command
{
public function __construct(
private EnqueueDaemon $enqueueDaemon,
) {}
public function run(Params $params, IO $io): void
{
$daemonParams = $this->prepareParams($params);
$this->enqueueDaemon->run($daemonParams);
}
private function prepareParams(Params $params): EnqueueDaemon\Params
{
$interval = $this->getInterval($params);
$limit = $this->getLimit($params);
$portion = $this->getPortion($params);
$queue = $params->getOption('queue');
if ($queue === QueueName::M0) {
throw new InvalidArgument("Queue 'm0' is not allowed. It is processed in the main queue.");
}
return new EnqueueDaemon\Params(
interval: $interval,
limit: $limit,
portion: $portion,
queue: $queue,
);
}
private function getInterval(Params $params): ?float
{
$valueString = $params->getOption('interval');
if ($valueString === null) {
return null;
}
if (
filter_var($valueString, FILTER_VALIDATE_INT) !== false ||
filter_var($valueString, FILTER_VALIDATE_FLOAT) !== false
) {
return (float) $valueString;
}
throw new RuntimeException("Bad interval.");
}
private function getLimit(Params $params): ?int
{
$valueString = $params->getOption('limit');
if ($valueString === null) {
return null;
}
if (filter_var($valueString, FILTER_VALIDATE_INT) !== false) {
return (int) $valueString;
}
throw new RuntimeException("Bad limit.");
}
private function getPortion(Params $params): ?int
{
$valueString = $params->getOption('portion');
if ($valueString === null) {
return null;
}
if (filter_var($valueString, FILTER_VALIDATE_INT) !== false) {
return (int) $valueString;
}
throw new RuntimeException("Bad portion.");
}
}

View File

@@ -0,0 +1,94 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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\Classes\ConsoleCommands;
use Espo\Core\Console\Command;
use Espo\Core\Console\Command\Params;
use Espo\Core\Console\IO;
use Espo\Core\Job\Processing\PrepareDaemon;
use RuntimeException;
/**
* @noinspection PhpUnused
*/
class JobPrepare implements Command
{
public function __construct(
private PrepareDaemon $prepareDaemon,
) {}
public function run(Params $params, IO $io): void
{
$interval = $this->getInterval($params);
$limit = $this->getLimit($params);
$skipQueues = $params->hasFlag('sq');
$daemonParams = new PrepareDaemon\Params(
interval: $interval,
limit: $limit,
skipQueues: $skipQueues,
);
$this->prepareDaemon->run($daemonParams);
}
private function getInterval(Params $params): ?float
{
$intervalString = $params->getOption('interval');
if ($intervalString === null) {
return null;
}
if (
filter_var($intervalString, FILTER_VALIDATE_INT) !== false ||
filter_var($intervalString, FILTER_VALIDATE_FLOAT) !== false
) {
return (float) $intervalString;
} else {
throw new RuntimeException("Bad interval.");
}
}
private function getLimit(Params $params): ?int
{
$limitString = $params->getOption('limit');
if ($limitString === null) {
return null;
}
if (filter_var($limitString, FILTER_VALIDATE_INT) !== false) {
return (int) $limitString;
} else {
throw new RuntimeException("Bad limit.");
}
}
}

View File

@@ -0,0 +1,86 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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\Classes\ConsoleCommands;
use Espo\Core\Console\Command;
use Espo\Core\Console\Command\Params;
use Espo\Core\Console\Exceptions\InvalidArgument;
use Espo\Core\Console\IO;
use Espo\Core\Job\Processing\WorkerDaemon;
use Espo\Core\Job\QueueName;
use RuntimeException;
/**
* @noinspection PhpUnused
*/
class JobWorker implements Command
{
public function __construct(
private WorkerDaemon $workerDaemon,
) {}
public function run(Params $params, IO $io): void
{
$daemonParams = $this->prepareParams($params);
$this->workerDaemon->run($daemonParams);
}
private function prepareParams(Params $params): WorkerDaemon\Params
{
$limit = $this->getLimit($params);
$queue = $params->getOption('queue');
if ($queue === QueueName::M0) {
throw new InvalidArgument("Queue 'm0' is not allowed. It is processed in the main queue.");
}
return new WorkerDaemon\Params(
limit: $limit,
queue: $queue,
);
}
private function getLimit(Params $params): ?int
{
$limitString = $params->getOption('limit');
if ($limitString === null) {
return null;
}
if (filter_var($limitString, FILTER_VALIDATE_INT) !== false) {
return (int) $limitString;
}
throw new RuntimeException("Bad limit.");
}
}

View File

@@ -0,0 +1,108 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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\Classes\ConsoleCommands;
use Espo\Core\Console\Command;
use Espo\Core\Console\Command\Params;
use Espo\Core\Console\IO;
use Espo\Core\Exceptions\Error;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\Entities\ArrayValue;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
use Espo\Repositories\ArrayValue as ArrayValueRepository;
class PopulateArrayValues implements Command
{
private EntityManager $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* @throws Error
*/
public function run(Params $params, IO $io): void
{
$entityType = $params->getArgument(0);
$field = $params->getArgument(1);
if (!$entityType || !$field) {
throw new Error("Entity type and field should be passed as arguments.");
}
if (!$this->entityManager->hasRepository($entityType)) {
throw new Error("Bad entity type.");
}
$defs = $this->entityManager->getDefs()->getEntity($entityType);
if (!$defs->hasAttribute($field)) {
throw new Error("Bad field.");
}
if ($defs->getAttribute($field)->getType() !== Entity::JSON_ARRAY) {
throw new Error("Non-array field.");
}
if ($defs->getAttribute($field)->isNotStorable()) {
throw new Error("Not-storable field.");
}
if (!$defs->getAttribute($field)->getParam('storeArrayValues')) {
throw new Error("Array values disabled for the field..");
}
$collection = $this->entityManager
->getRDBRepository($entityType)
->sth()
->find();
/** @var ArrayValueRepository $repository */
$repository = $this->entityManager->getRepository(ArrayValue::ENTITY_TYPE);
foreach ($collection as $i => $entity) {
if (!$entity instanceof CoreEntity) {
throw new Error();
}
$repository->storeEntityAttribute($entity, $field);
if ($i % 1000 === 0) {
$io->write('.');
}
}
$io->writeLine('');
$io->writeLine('Done.');
}
}

View File

@@ -0,0 +1,116 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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\Classes\ConsoleCommands;
use Espo\Core\Console\Command;
use Espo\Core\Console\Command\Params;
use Espo\Core\Console\Exceptions\ArgumentNotSpecified;
use Espo\Core\Console\Exceptions\InvalidArgument;
use Espo\Core\Console\IO;
use Espo\Core\Exceptions\Error;
use Espo\Core\FieldProcessing\NextNumber\Processor;
use Espo\Core\Name\Field;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\Core\ORM\Repository\Option\SaveOption;
use Espo\ORM\EntityManager;
use Espo\ORM\Query\Part\Order;
class PopulateNumbers implements Command
{
private Processor $beforeSaveProcessor;
private EntityManager $entityManager;
public function __construct(
Processor $beforeSaveProcessor,
EntityManager $entityManager
) {
$this->beforeSaveProcessor = $beforeSaveProcessor;
$this->entityManager = $entityManager;
}
/**
* @throws Error
*/
public function run(Params $params, IO $io): void
{
$entityType = $params->getArgument(0);
$field = $params->getArgument(1);
$orderBy = $params->getOption('orderBy') ?? Field::CREATED_AT;
$order = strtoupper($params->getOption('order') ?? Order::ASC);
if (!$entityType) {
throw new ArgumentNotSpecified("No entity type argument.");
}
if (!$field) {
throw new ArgumentNotSpecified("No field argument.");
}
if ($order !== Order::ASC && $order !== Order::DESC) {
throw new InvalidArgument("Bad order option.");
}
$fieldType = $this->entityManager
->getDefs()
->getEntity($entityType)
->getField($field)
->getType();
if ($fieldType !== 'number') {
throw new InvalidArgument("Field `{$field}` is not of `number` type.");
}
$collection = $this->entityManager
->getRDBRepository($entityType)
->where([
$field => null,
])
->order($orderBy, $order)
->sth()
->find();
foreach ($collection as $i => $entity) {
if (!$entity instanceof CoreEntity) {
throw new Error();
}
$this->beforeSaveProcessor->processPopulate($entity, $field);
$this->entityManager->saveEntity($entity, [SaveOption::IMPORT => true]);
if ($i % 1000 === 0) {
$io->write('.');
}
}
$io->writeLine('');
$io->writeLine('Done.');
}
}

View File

@@ -0,0 +1,62 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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\Classes\ConsoleCommands;
use Espo\Core\Console\Command;
use Espo\Core\Console\Command\Params;
use Espo\Core\Console\IO;
use Espo\Core\Utils\ScheduledJob\Populator;
/**
* @noinspection PhpUnused
*/
class PopulateScheduledJobs implements Command
{
public function __construct(
private Populator $populator,
) {}
public function run(Params $params, IO $io): void
{
$names = $this->populator->populate();
if ($names === []) {
$io->writeLine("Nothing to create.");
return;
}
$io->writeLine("Created scheduled jobs:");
foreach ($names as $name) {
$io->writeLine(' ' . $name);
}
}
}

View File

@@ -0,0 +1,69 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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\Classes\ConsoleCommands;
use Espo\Core\Console\Command;
use Espo\Core\Console\Command\Params;
use Espo\Core\Console\IO;
use Espo\Tools\CategoryTree\RebuildPaths;
use Exception;
class RebuildCategoryPaths implements Command
{
private RebuildPaths $rebuildPaths;
public function __construct(RebuildPaths $rebuildPaths)
{
$this->rebuildPaths = $rebuildPaths;
}
public function run(Params $params, IO $io): void
{
$entityType = $params->getArgument(0);
if (!$entityType) {
$io->setExitStatus(1);
$io->writeLine("Error: No entity type. Should be specified as the first argument.");
return;
}
try {
$this->rebuildPaths->run($entityType);
} catch (Exception $e) {
$io->setExitStatus(1);
$io->writeLine("Error: " . $e->getMessage());
return;
}
$io->writeLine("Done.");
}
}

View File

@@ -0,0 +1,219 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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\Classes\ConsoleCommands;
use Espo\Core\Console\Command;
use Espo\Core\Console\Command\Params;
use Espo\Core\Console\IO;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Config\ConfigWriter;
use Espo\Core\Utils\Json;
use Espo\Core\Utils\ObjectUtil;
use Exception;
use stdClass;
/**
* @noinspection PhpUnused
*/
class SetConfigParam implements Command
{
private const string TYPE_STRING = 'string';
private const string TYPE_BOOL = 'bool';
private const string TYPE_INT = 'int';
private const string TYPE_FLOAT = 'float';
private const string TYPE_JSON = 'json';
private const string TYPE_AUTO = 'auto';
/** @var string[] */
private const array ALLOWED_TYPES = [
self::TYPE_STRING,
self::TYPE_BOOL,
self::TYPE_INT,
self::TYPE_FLOAT,
self::TYPE_JSON,
self::TYPE_AUTO,
];
public function __construct(
private Config $config,
private ConfigWriter $configWriter,
) {}
public function run(Params $params, IO $io): void
{
$type = $params->getOption('type') ?? self::TYPE_STRING;
if (!in_array($type, self::ALLOWED_TYPES)) {
$io->writeLine("Not allowed type.");
$io->setExitStatus(1);
return;
}
$name = $params->getArgument(0) ?? null;
$value = $params->getArgument(1) ?? null;
if ($name === null) {
$io->writeLine("Parameter name and value are not specified.");
$io->setExitStatus(1);
return;
}
if ($value === null) {
$io->writeLine("Value is not specified.");
$io->setExitStatus(1);
return;
}
$value = $this->prepareValue($io, $value, $type);
if ($io->getExitStatus() !== 0) {
return;
}
if (str_contains($name, '.')) {
if (substr_count($name, '.') > 1) {
$io->writeLine("Cannot use a parameter name with more than one dot.");
$io->setExitStatus(1);
return;
}
[$nameOne, $subName] = explode('.', $name);
if ($nameOne === '' || $subName === '') {
$io->writeLine("Bad parameter name.");
$io->setExitStatus(1);
return;
}
$setValue = $this->config->get($nameOne) ?? [];
if ($setValue instanceof stdClass) {
$setValue = ObjectUtil::clone($setValue);
$setValue->$subName = $value;
} else if (is_array($setValue) && !array_is_list($setValue)) {
$setValue[$subName] = $value;
} else {
$io->writeLine("No parameter parameter '$nameOne' in config.");
$io->setExitStatus(1);
return;
}
$value = $setValue;
$name = $nameOne;
}
$this->configWriter->set($name, $value);
$this->configWriter->save();
}
private function prepareValue(IO $io, string $value, string $type): mixed
{
if ($type === self::TYPE_AUTO) {
$lower = strtolower($value);
if ($lower === 'true') {
return true;
}
if ($lower === 'false') {
return false;
}
if ($lower === 'null') {
return null;
}
if (filter_var($value, FILTER_VALIDATE_INT) !== false) {
return (int) $value;
}
if (filter_var($value, FILTER_VALIDATE_FLOAT) !== false) {
return (float) $value;
}
return $value;
}
if ($type === self::TYPE_JSON) {
try {
return Json::decode($value);
} catch (Exception) {
$io->writeLine("Invalid JSON.");
$io->setExitStatus(1);
return null;
}
}
if ($type === self::TYPE_INT) {
if (!is_numeric($value)) {
$io->writeLine("Value is not numeric.");
$io->setExitStatus(1);
return null;
}
return (int) $value;
}
if ($type === self::TYPE_FLOAT) {
if (!is_numeric($value)) {
$io->writeLine("Value is not numeric.");
$io->setExitStatus(1);
return null;
}
return (float) $value;
}
if ($type === self::TYPE_BOOL) {
$value = strtolower($value);
if (!in_array($value, ['true', 'false', '1', '0'])) {
$io->writeLine("Value is not boolean.");
$io->setExitStatus(1);
return null;
}
return $value === 'true' || $value === '1';
}
return $value;
}
}