This commit is contained in:
root
2025-11-13 19:52:28 +03:00
parent 8aeeb05b7d
commit 807dec3b6c
4646 changed files with 163445 additions and 626017 deletions

View File

@@ -0,0 +1,212 @@
<?php
namespace Bitrix\Main;
use Bitrix\Main\IO\Directory;
use Bitrix\Main\IO\File;
final class ClassLocator
{
private static array $loadedClasses = [];
public static function getClassesByNamespace(string $namespace): array
{
$result = [];
$directories = self::getDirectoriesByNamespace($namespace);
foreach ($directories as $directory)
{
$classes = self::getClassesByPath($directory);
if (!empty($classes))
{
$result = array_merge($result, $classes);
}
}
return $result;
}
private static function getDirectoriesByNamespace(string $namespaceDirectory): array
{
$originalNamespaceDirectory = trim($namespaceDirectory, '\\');
$namespaceDirectory = self::normalizeNamespace($namespaceDirectory);
$namespaces = Loader::getNamespaces();
$namespaceDirectories = $namespaces[$namespaceDirectory] ?? [];
$directories = [];
if (empty($namespaceDirectories))
{
$namespaceParts = explode('\\', trim($namespaceDirectory, '\\'));
$originalNamespaceParts = explode('\\', $originalNamespaceDirectory);
for ($i = count($namespaceParts); $i >= 0; $i--)
{
$partNamespace = join('\\', array_slice($namespaceParts, 0, $i)) . '\\';
$partNamespaceDirectories = $namespaces[$partNamespace] ?? null;
if (empty($partNamespaceDirectories))
{
continue;
}
foreach ($partNamespaceDirectories as $partNamespaceDirectoriesItem)
{
$originalStylePath =
$partNamespaceDirectoriesItem['path']
. DIRECTORY_SEPARATOR
. join(DIRECTORY_SEPARATOR, array_slice($originalNamespaceParts, $i))
;
if (is_dir($originalStylePath))
{
$directories[] = $originalStylePath;
continue;
}
$lowerStylePath =
$partNamespaceDirectoriesItem['path']
. DIRECTORY_SEPARATOR
. join(DIRECTORY_SEPARATOR, array_slice($namespaceParts, $i))
;
if (is_dir($lowerStylePath))
{
$directories[] = $lowerStylePath;
continue;
}
}
}
}
else
{
foreach ($namespaceDirectories as $namespaceItem)
{
$directories[] = $namespaceItem['path'];
}
}
$directories = array_unique($directories);
return array_unique($directories);
}
private static function getClassesByPath($path): array
{
if (isset(self::$loadedClasses[$path]))
{
return self::$loadedClasses[$path];
}
$pathFiles = self::getFilesByPath($path);
foreach ($pathFiles as $pathFile)
{
$classesNames = self::getClassesNamesFromFilePath($pathFile);
foreach ($classesNames as $className)
{
if (class_exists($className))
{
self::$loadedClasses[$path][] = $className;
}
}
}
return self::$loadedClasses[$path] ?? [];
}
private static function getFilesByPath(string $path): array
{
$files = [];
$directory = new Directory($path);
if ($directory->isExists())
{
foreach ($directory->getChildren() as $child)
{
if ($child instanceof File)
{
$files[] = $child->getPath();
}
elseif ($child instanceof Directory)
{
foreach (self::getFilesByPath($child->getPath()) as $file)
{
$files[] = $file;
}
}
}
}
else
{
throw new ArgumentException('Invalid directory path');
}
return $files;
}
private static function getClassesNamesFromFilePath(string $filePath): array
{
$classes = [];
$file = new File($filePath);
if ($file->isExists() && $file->getExtension() === 'php')
{
$namespace = '';
$tokens = token_get_all($file->getContents());
$tokenCount = count($tokens);
for ($i = 0; $i < $tokenCount; $i++)
{
if (!is_array($tokens[$i]))
{
continue;
}
$tokenType = $tokens[$i][0];
if ($tokenType === T_NAMESPACE)
{
$namespace = '';
for ($j = $i + 1; $j < $tokenCount; $j++)
{
if ($tokens[$j] === ';')
{
break;
}
if (is_array($tokens[$j]) && in_array($tokens[$j][0], [T_NAME_QUALIFIED, T_STRING, T_NS_SEPARATOR]))
{
$namespace .= $tokens[$j][1];
}
}
continue;
}
if ($tokenType === T_CLASS)
{
$classNameToken = $i + 1;
while (
isset($tokens[$classNameToken]) &&
is_array($tokens[$classNameToken]) &&
$tokens[$classNameToken][0] === T_WHITESPACE
)
{
$classNameToken++;
}
if (
isset($tokens[$classNameToken]) &&
is_array($tokens[$classNameToken]) &&
$tokens[$classNameToken][0] === T_STRING
)
{
$className = $tokens[$classNameToken][1];
$classes[] = $namespace ? $namespace . '\\' . $className : $className;
}
}
}
}
return $classes;
}
private static function normalizeNamespace($namespace): string
{
return trim(strtolower($namespace), '\\') . '\\';
}
}

View File

@@ -34,7 +34,11 @@ abstract class AbstractCommand implements CommandInterface
}
catch (\Exception $e)
{
throw new CommandException($this, 'Command has unprocessed exception', previous: $e);
throw new CommandException(
$this,
sprintf('Command has unprocessed exception: "%s". Code: "%s"', $e->getMessage(), $e->getCode()),
previous: $e
);
}
$this->afterRun();

View File

@@ -53,7 +53,7 @@ class EntityCollection implements EntityCollectionInterface
foreach ($this->items as $key => $item)
{
if (call_user_func($callback, $item, $key))
if ($callback($item, $key))
{
return $item;
}

View File

@@ -184,6 +184,16 @@ class Image
return $this->engine->resize($source, $destination);
}
/**
* Blurs the image.
* @param int $sigma
* @return bool
*/
public function blur(int $sigma): bool
{
return $this->engine->blur($sigma);
}
/**
* Applies a mask to the image (convolution).
* @param Image\Mask $mask

View File

@@ -198,6 +198,15 @@ abstract class Engine
*/
abstract public function resize(Rectangle $source, Rectangle $destination);
/**
* Blur the image.
*
* @param int $sigma
*
* @return bool
*/
abstract public function blur(int $sigma): bool;
/**
* Applies a mask to the image (convolution).
* @param Mask $mask

View File

@@ -173,6 +173,72 @@ class Gd extends Engine
return false;
}
/**
* @inheritDoc
*/
public function blur(int $sigma): bool
{
if ($this->resource === null)
{
return false;
}
$sigma = max(1, min(100, round($sigma)));
$originalWidth = $this->getWidth();
$originalHeight = $this->getHeight();
$minScale = 0.5;
$smallestWidth = ceil($originalWidth * (1 - pow($minScale, 1.0 / $sigma)));
$smallestHeight = ceil($originalHeight * (1 - pow($minScale, 1.0 / $sigma)));
$prevImage = $this->resource;
$prevWidth = $originalWidth;
$prevHeight = $originalHeight;
$nextImage = $this->resource;
$nextWidth = 0;
$nextHeight = 0;
for ($i = 1; $i <= $sigma; $i += 1)
{
$denominator = (1 - pow($minScale, 1.0 / $i));
$nextWidth = (int)round($smallestWidth / $denominator);
$nextHeight = (int)round($smallestHeight / $denominator);
$nextImage = imagecreatetruecolor($nextWidth, $nextHeight);
if ($this->format == File\Image::FORMAT_PNG || $this->format == File\Image::FORMAT_WEBP)
{
imagealphablending($nextImage, false);
imagesavealpha($nextImage, true);
}
imagecopyresampled($nextImage, $prevImage, 0, 0, 0, 0, $nextWidth, $nextHeight, $prevWidth, $prevHeight);
imagefilter($nextImage, IMG_FILTER_GAUSSIAN_BLUR);
if ($prevImage !== $this->resource)
{
imagedestroy($prevImage);
}
$prevImage = $nextImage;
$prevWidth = $nextWidth;
$prevHeight = $nextHeight;
}
if ($this->format == File\Image::FORMAT_PNG || $this->format == File\Image::FORMAT_WEBP)
{
imagealphablending($this->resource, false);
imagesavealpha($this->resource, true);
}
imagecopyresampled($this->resource, $nextImage, 0, 0, 0, 0, $originalWidth, $originalHeight, $nextWidth, $nextHeight);
imagefilter($this->resource, IMG_FILTER_GAUSSIAN_BLUR);
if ($nextImage !== $this->resource)
{
imagedestroy($nextImage);
}
return true;
}
/**
* @inheritDoc
*/

View File

@@ -264,6 +264,22 @@ class Imagick extends Engine
return true;
}
/**
* @inheritDoc
*/
public function blur(int $sigma): bool
{
if ($this->image === null)
{
return false;
}
$sigma = max(1, min(100, round($sigma)));
$this->image->blurImage(0, $sigma);
return true;
}
/**
* @inheritDoc
*/

View File

@@ -4,11 +4,11 @@ declare(strict_types=1);
namespace Bitrix\Main\Messenger\Internals\Storage\Db\Model;
use Bitrix\Main\Entity\IntegerField;
use Bitrix\Main\Entity\TextField;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Event;
use Bitrix\Main\ORM\EventResult;
use Bitrix\Main\ORM\Fields\IntegerField;
use Bitrix\Main\ORM\Fields\TextField;
use Bitrix\Main\ORM\Fields\DatetimeField;
use Bitrix\Main\ORM\Fields\StringField;
use Bitrix\Main\Type\DateTime;

View File

@@ -9,4 +9,5 @@ enum Type: string
case Integer = 'is_int';
case String = 'is_string';
case Float = 'is_float';
case Numeric = 'is_numeric';
}

View File

@@ -34,10 +34,10 @@ class AccessCode
AC_ACCESS_TEAM_DIRECTOR = '^(' . self::ACCESS_TEAM_DIRECTOR . ')(\d+)?$',
AC_ACCESS_TEAM_EMPLOYEE = '^(' . self::ACCESS_TEAM_EMPLOYEE . ')(\d+)?$',
AC_ACCESS_TEAM_DEPUTY = '^(' . self::ACCESS_TEAM_DEPUTY . ')(\d+)?$',
AC_STRUCTURE_TEAM = '^(SNT)(\d+)$',
AC_ALL_STRUCTURE_TEAM = '^(SNTR)(\d+)$',
AC_STRUCTURE_DEPARTMENT = '^(SND)(\d+)$',
AC_ALL_STRUCTURE_DEPARTMENT = '^(SNDR)(\d+)$';
AC_ALL_STRUCTURE_DEPARTMENT = '^(SNDR)(\d+)$',
AC_STRUCTURE_TEAM = '^(SNT)(\d+)$',
AC_ALL_STRUCTURE_TEAM = '^(SNTR)(\d+)$';
public const
TYPE_USER = 'users',
@@ -53,8 +53,8 @@ class AccessCode
TYPE_ACCESS_TEAM_DEPUTY = 'access_team_deputy',
TYPE_CHAT = 'chat',
TYPE_OTHER = 'other',
TYPE_STRUCTURE_TEAM = 'structureteams',
TYPE_STRUCTURE_DEPARTMENT = 'structuredepartments';
TYPE_STRUCTURE_DEPARTMENT = 'structuredepartments',
TYPE_STRUCTURE_TEAM = 'structureteams';
public static $map = [
self::AC_DEPARTMENT => self::TYPE_DEPARTMENT,
@@ -71,10 +71,10 @@ class AccessCode
self::AC_ACCESS_TEAM_DIRECTOR => self::TYPE_ACCESS_TEAM_DIRECTOR,
self::AC_ACCESS_TEAM_EMPLOYEE => self::TYPE_ACCESS_TEAM_EMPLOYEE,
self::AC_ACCESS_TEAM_DEPUTY => self::TYPE_ACCESS_TEAM_DEPUTY,
self::AC_STRUCTURE_DEPARTMENT => self::TYPE_STRUCTURE_DEPARTMENT,
self::AC_ALL_STRUCTURE_DEPARTMENT => self::TYPE_STRUCTURE_DEPARTMENT,
self::AC_STRUCTURE_TEAM => self::TYPE_STRUCTURE_TEAM,
self::AC_ALL_STRUCTURE_TEAM => self::TYPE_STRUCTURE_TEAM,
self::AC_STRUCTURE_DEPARTMENT => self::TYPE_STRUCTURE_DEPARTMENT,
self::AC_ALL_STRUCTURE_DEPARTMENT => self::TYPE_STRUCTURE_DEPARTMENT,
];
private $accessCode;

View File

@@ -8,10 +8,10 @@
namespace Bitrix\Main\Access\Entity;
use Bitrix\Main\Entity;
use Bitrix\Main\ORM\Query\Query;
use Bitrix\Main\ORM\Data;
class DataManager extends Entity\DataManager
class DataManager extends Data\DataManager
{
public static function deleteList(array $filter)
{
@@ -24,4 +24,4 @@ class DataManager extends Entity\DataManager
Query::buildFilterSql($entity, $filter)
));
}
}
}

View File

@@ -1,4 +1,5 @@
<?php
/**
* Bitrix Framework
* @package bitrix
@@ -8,12 +9,13 @@
namespace Bitrix\Main\Access\Permission;
use Bitrix\Main\Entity;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\NotSupportedException;
use Bitrix\Main\ORM\Data\Result;
use Bitrix\Main\ORM\Event;
use Bitrix\Main\Access\Entity\DataManager;
use Bitrix\Main\ORM\Fields;
use Bitrix\Main\ORM\EntityError;
Loc::loadMessages(__FILE__);
@@ -22,17 +24,17 @@ abstract class AccessPermissionTable extends DataManager
public static function getMap()
{
return [
new Entity\IntegerField('ID', [
new Fields\IntegerField('ID', [
'autocomplete' => true,
'primary' => true
]),
new Entity\IntegerField('ROLE_ID', [
new Fields\IntegerField('ROLE_ID', [
'required' => true
]),
new Entity\StringField('PERMISSION_ID', [
new Fields\StringField('PERMISSION_ID', [
'required' => true
]),
new Entity\IntegerField('VALUE', [
new Fields\IntegerField('VALUE', [
'required' => true
])
];
@@ -51,7 +53,7 @@ abstract class AccessPermissionTable extends DataManager
{
if (empty($primary))
{
$result->addError(new Entity\EntityError(Loc::getMessage('ACCESS_PERMISSION_PARENT_VALIDATE_ERROR')));
$result->addError(new EntityError(Loc::getMessage('ACCESS_PERMISSION_PARENT_VALIDATE_ERROR')));
return;
}
$data = static::loadUpdateRow($primary, $data);
@@ -59,7 +61,7 @@ abstract class AccessPermissionTable extends DataManager
if (!static::validateRow($data))
{
$result->addError(new Entity\EntityError(Loc::getMessage('ACCESS_PERMISSION_PARENT_VALIDATE_ERROR')));
$result->addError(new EntityError(Loc::getMessage('ACCESS_PERMISSION_PARENT_VALIDATE_ERROR')));
}
}
@@ -180,4 +182,4 @@ abstract class AccessPermissionTable extends DataManager
return true;
}
}
}

View File

@@ -1,4 +1,5 @@
<?php
/**
* Bitrix Framework
* @package bitrix
@@ -8,24 +9,24 @@
namespace Bitrix\Main\Access\Role;
use Bitrix\Main\Entity;
use Bitrix\Main\Access\Entity\DataManager;
use Bitrix\Main\ORM\Fields;
abstract class AccessRoleRelationTable extends DataManager
{
public static function getMap()
{
return [
new Entity\IntegerField('ID', [
new Fields\IntegerField('ID', [
'autocomplete' => true,
'primary' => true
]),
new Entity\IntegerField('ROLE_ID', [
new Fields\IntegerField('ROLE_ID', [
'required' => true
]),
new Entity\StringField('RELATION', [
new Fields\StringField('RELATION', [
'required' => true
])
];
}
}
}

View File

@@ -7,19 +7,19 @@
*/
namespace Bitrix\Main\Access\Role;
use Bitrix\Main\Entity;
use Bitrix\Main\Access\Entity\DataManager;
use Bitrix\Main\ORM\Fields;
abstract class AccessRoleTable extends DataManager
{
public static function getMap()
{
return [
new Entity\IntegerField('ID', [
new Fields\IntegerField('ID', [
'autocomplete' => true,
'primary' => true
]),
new Entity\StringField('NAME', [
new Fields\StringField('NAME', [
'required' => true,
])
];

View File

@@ -7,8 +7,9 @@
*/
namespace Bitrix\Main\Analytics;
use Bitrix\Main\Entity;
use Bitrix\Main\Security\Random;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields;
/**
* Class CounterDataTable
@@ -26,7 +27,7 @@ use Bitrix\Main\Security\Random;
* @method static \Bitrix\Main\Analytics\EO_CounterData wakeUpObject($row)
* @method static \Bitrix\Main\Analytics\EO_CounterData_Collection wakeUpCollection($rows)
*/
class CounterDataTable extends Entity\DataManager
class CounterDataTable extends DataManager
{
public static function getTableName()
{
@@ -36,14 +37,14 @@ class CounterDataTable extends Entity\DataManager
public static function getMap()
{
return array(
new Entity\StringField('ID', array(
new Fields\StringField('ID', array(
'primary' => true,
'default_value' => array(__CLASS__ , 'getUniqueEventId')
)),
new Entity\StringField('TYPE', array(
new Fields\StringField('TYPE', array(
'required' => true
)),
new Entity\TextField('DATA', array(
new Fields\TextField('DATA', array(
'serialized' => true
))
);
@@ -51,7 +52,7 @@ class CounterDataTable extends Entity\DataManager
public static function getUniqueEventId()
{
list($usec, $sec) = explode(" ", microtime());
[$usec, $sec] = explode(" ", microtime());
$uniqid = mb_substr(base_convert($sec.mb_substr($usec, 2), 10, 36), 0, 16);

View File

@@ -4,7 +4,7 @@
* Bitrix Framework
* @package bitrix
* @subpackage main
* @copyright 2001-2024 Bitrix
* @copyright 2001-2025 Bitrix
*/
namespace Bitrix\Main\Authentication;
@@ -21,6 +21,10 @@ use Bitrix\Main\Service\GeoIp;
use Bitrix\Main\Service\GeoIp\Internal\GeonameTable;
use Bitrix\Main\Mail;
use Bitrix\Main\Localization\LanguageTable;
use Bitrix\Main\Localization\Loc;
use Bitrix\ImBot\Bot\Marta;
use Bitrix\Im\V2\Message;
use Bitrix\Im\V2\Entity\User\User;
class Device
{
@@ -53,10 +57,13 @@ class Device
$device = static::add($context);
$deviceLogin = static::addDeviceLogin($device, $context);
if (Option::get('main', 'user_device_notify', 'N') === 'Y')
$notifyByEmail = (Option::get('main', 'user_device_notify', 'N') === 'Y');
$notifyByIm = (Option::get('main', 'user_device_notify_im', 'N') === 'Y' && Main\ModuleManager::isModuleInstalled('im') && Main\ModuleManager::isModuleInstalled('imbot'));
if ($notifyByEmail || $notifyByIm)
{
// send notification to the user
static::sendEmail($device, $deviceLogin, $user);
static::notifyUser($device, $deviceLogin, $user);
}
}
else
@@ -219,15 +226,17 @@ class Device
return $login;
}
protected static function sendEmail(EO_UserDevice $device, EO_UserDeviceLogin $deviceLogin, array $user): void
protected static function notifyUser(EO_UserDevice $device, EO_UserDeviceLogin $deviceLogin, array $user): void
{
$site = $user['LID'];
if (!$site)
// we have settings in the main module options
$codes = unserialize(Option::get('main', 'user_device_notify_codes'), ['allowed_classes' => false]);
if (!empty($codes))
{
$site = Main\Context::getCurrent()->getSite();
if (!$site)
$userCodes = \CAccess::GetUserCodesArray($user['ID']);
if (empty(array_intersect($codes, $userCodes)))
{
$site = \CSite::GetDefSite();
// no need to notify
return;
}
}
@@ -237,6 +246,83 @@ class Device
// Devices
$deviceTypes = DeviceType::getDescription($lang);
// geoIP
$geonames = [];
if (Option::get('main', 'user_device_geodata', 'N') === 'Y')
{
$geonames = static::getGeoNames($deviceLogin, $lang);
}
$fields = [
'USER_ID' => $user['ID'],
'EMAIL' => $user['EMAIL'],
'LOGIN' => $user['LOGIN'],
'NAME' => $user['NAME'],
'LAST_NAME' => $user['LAST_NAME'],
'DEVICE' => $deviceTypes[$device->getDeviceType()],
'BROWSER' => $device->getBrowser(),
'PLATFORM' => $device->getPlatform(),
'USER_AGENT' => $device->getUserAgent(),
'IP' => $deviceLogin->getIp(),
'DATE' => $deviceLogin->getLoginDate(),
'COUNTRY' => $geonames['COUNTRY'] ?? '',
'REGION' => $geonames['REGION'] ?? '',
'CITY' => $geonames['CITY'] ?? '',
'LOCATION' => $geonames['LOCATION'] ?? '',
];
if (Option::get('main', 'user_device_notify', 'N') === 'Y')
{
// email
$site = $user['LID'];
if (!$site)
{
$site = Main\Context::getCurrent()->getSite();
if (!$site)
{
$site = \CSite::GetDefSite();
}
}
Mail\Event::send([
'EVENT_NAME' => self::EMAIL_EVENT,
'C_FIELDS' => $fields,
'LID' => $site,
'LANGUAGE_ID' => $lang,
]);
}
if (Option::get('main', 'user_device_notify_im', 'N') === 'Y' && Main\Loader::includeModule('im') && Main\Loader::includeModule('imbot'))
{
// chat notification
$replace = [];
foreach ($fields as $key => $value)
{
$replace["#$key#"] = $value;
}
$message = Loc::getMessage('main_device_message', $replace, $lang);
$pushMessage = Loc::getMessage('main_device_push_message', null, $lang);
$infoBotId = Marta::getBotId();
if ($infoBotId)
{
$chat = User::getInstance($user['ID'])->getChatWith($infoBotId);
if ($chat)
{
$chatMessage = new Message();
$chatMessage
->setMessage($message)
->setPushMessage($pushMessage)
->setAuthorId($infoBotId)
;
$chat->sendMessage($chatMessage);
}
}
}
}
protected static function getGeoNames(EO_UserDeviceLogin $deviceLogin, string $lang): array
{
// City and Region names
$geoids = array_filter([$deviceLogin->getCityGeoid(), $deviceLogin->getRegionGeoid()]);
$geonames = GeonameTable::get($geoids);
@@ -245,11 +331,13 @@ class Device
$region = '';
if (!empty($geonames))
{
$currentLang = Main\Context::getCurrent()->getLanguage();
$langCode = '';
if ($user['LANGUAGE_ID'] != '' && $user['LANGUAGE_ID'] != $currentLang)
if ($lang != $currentLang)
{
$language = LanguageTable::getList([
'filter' => ['=LID' => $user['LANGUAGE_ID'], '=ACTIVE' => 'Y'],
'filter' => ['=LID' => $lang, '=ACTIVE' => 'Y'],
'cache' => ['ttl' => 86400],
])->fetchObject();
@@ -284,29 +372,12 @@ class Device
// Combined location
$location = implode(', ', array_filter([$city, $region, $country]));
$fields = [
'EMAIL' => $user['EMAIL'],
'LOGIN' => $user['LOGIN'],
'NAME' => $user['NAME'],
'LAST_NAME' => $user['LAST_NAME'],
'DEVICE' => $deviceTypes[$device->getDeviceType()],
'BROWSER' => $device->getBrowser(),
'PLATFORM' => $device->getPlatform(),
'USER_AGENT' => $device->getUserAgent(),
'IP' => $deviceLogin->getIp(),
'DATE' => $deviceLogin->getLoginDate(),
return [
'COUNTRY' => $country,
'REGION' => $region,
'CITY' => $city,
'LOCATION' => $location,
];
Mail\Event::send([
'EVENT_NAME' => self::EMAIL_EVENT,
'C_FIELDS' => $fields,
'LID' => $site,
'LANGUAGE_ID' => $lang,
]);
}
/**

View File

@@ -138,9 +138,13 @@ class UpdaterService
return $result->addError(new Error($error));
}
\CUpdateClient::logUpdates($updateDescription["DATA"]["#"]["ITEM"]);
\CUpdateClient::finalizeModuleUpdate($updateDescription["DATA"]["#"]["ITEM"]);
$io->writeln('Done!');
Option::set('main', 'update_system_update_time', time());
}
}
@@ -358,6 +362,11 @@ class UpdaterService
}
}
foreach ($itemsUpdated as $key => $value)
{
\CUpdateClient::AddMessage2Log("Updated: {$key}" . ($value != '' ? " ({$value})" : ""), "UPD_SUCCESS");
}
\CUpdateClient::finalizeLanguageUpdate($itemsUpdated);
}

View File

@@ -1,7 +1,8 @@
<?php
namespace Bitrix\Main\Component;
use Bitrix\Main\Entity\DataManager;
use Bitrix\Main\ORM\Data;
/**
* Class ParametersTable
@@ -19,8 +20,7 @@ use Bitrix\Main\Entity\DataManager;
* @method static \Bitrix\Main\Component\EO_Parameters wakeUpObject($row)
* @method static \Bitrix\Main\Component\EO_Parameters_Collection wakeUpCollection($rows)
*/
class ParametersTable
extends DataManager
class ParametersTable extends Data\DataManager
{
const SEF_MODE = 'Y';
const NOT_SEF_MODE = 'N';
@@ -79,7 +79,7 @@ class ParametersTable
if (empty($siteId))
throw new \Bitrix\Main\ArgumentNullException("siteId");
$result = new \Bitrix\Main\Entity\DeleteResult();
$result = new Data\DeleteResult();
// event PRE
@@ -101,7 +101,7 @@ class ParametersTable
if (empty($filter))
throw new \Bitrix\Main\ArgumentNullException("filter");
$result = new \Bitrix\Main\Entity\DeleteResult();
$result = new Data\DeleteResult();
$dbResult = static::getList(
array(

View File

@@ -3,6 +3,7 @@
namespace Bitrix\Main\Config;
use Bitrix\Main;
use Bitrix\Main\Loader;
final class Configuration implements \ArrayAccess, \Iterator, \Countable
{
@@ -14,11 +15,11 @@ final class Configuration implements \ArrayAccess, \Iterator, \Countable
/**
* @var Configuration[]
*/
private static $instances;
private $moduleId = null;
private $storedData = null;
private $data = [];
private $isLoaded = false;
private static array $instances = [];
private ?string $moduleId = null;
private ?array $storedData = null;
private array $data = [];
private bool $isLoaded = false;
public static function getValue($name)
{
@@ -26,7 +27,7 @@ final class Configuration implements \ArrayAccess, \Iterator, \Countable
return $configuration->get($name);
}
public static function setValue($name, $value)
public static function setValue($name, $value): void
{
$configuration = Configuration::getInstance();
$configuration->add($name, $value);
@@ -41,13 +42,7 @@ final class Configuration implements \ArrayAccess, \Iterator, \Countable
}
}
/**
* @static
*
* @param string|null $moduleId
* @return Configuration
*/
public static function getInstance($moduleId = null)
public static function getInstance($moduleId = null): self
{
if (!isset(self::$instances[$moduleId]))
{
@@ -57,32 +52,32 @@ final class Configuration implements \ArrayAccess, \Iterator, \Countable
return self::$instances[$moduleId];
}
private static function getPathConfigForModule($moduleId)
private static function getPathConfigForModule($moduleId): ?string
{
if (!$moduleId || !Main\ModuleManager::isModuleInstalled($moduleId))
{
return false;
return null;
}
$moduleConfigPath = getLocalPath("modules/{$moduleId}/.settings.php");
$moduleConfigPath = Loader::getLocal("modules/{$moduleId}/.settings.php");
if ($moduleConfigPath === false)
{
return false;
return null;
}
return Main\Loader::getDocumentRoot() . $moduleConfigPath;
return $moduleConfigPath;
}
private function loadConfiguration()
private function loadConfiguration(): void
{
$this->isLoaded = false;
if ($this->moduleId)
{
$path = self::getPathConfigForModule($this->moduleId);
if (file_exists($path))
if ($path !== null && file_exists($path))
{
$dataTmp = include($path);
$dataTmp = include $path;
if (is_array($dataTmp))
{
$this->data = $dataTmp;
@@ -91,18 +86,18 @@ final class Configuration implements \ArrayAccess, \Iterator, \Countable
}
else
{
if (($path = getLocalPath(self::CONFIGURATION_FILE)) !== false)
if (($path = Loader::getLocal(self::CONFIGURATION_FILE)) !== false)
{
$dataTmp = include Main\Loader::getDocumentRoot() . $path;
$dataTmp = include $path;
if (is_array($dataTmp))
{
$this->data = $dataTmp;
}
}
if (($pathExtra = getLocalPath(self::CONFIGURATION_FILE_EXTRA)) !== false)
if (($pathExtra = Loader::getLocal(self::CONFIGURATION_FILE_EXTRA)) !== false)
{
$dataTmp = include Main\Loader::getDocumentRoot() . $pathExtra;
$dataTmp = include $pathExtra;
if (is_array($dataTmp) && !empty($dataTmp))
{
$this->storedData = $this->data;
@@ -117,7 +112,7 @@ final class Configuration implements \ArrayAccess, \Iterator, \Countable
$this->isLoaded = true;
}
public function saveConfiguration()
public function saveConfiguration(): void
{
if (!$this->isLoaded)
{
@@ -130,7 +125,7 @@ final class Configuration implements \ArrayAccess, \Iterator, \Countable
}
else
{
$path = Main\Loader::getDocumentRoot() . getLocalPath(self::CONFIGURATION_FILE);
$path = Loader::getLocal(self::CONFIGURATION_FILE);
}
$data = ($this->storedData !== null) ? $this->storedData : $this->data;
@@ -143,7 +138,7 @@ final class Configuration implements \ArrayAccess, \Iterator, \Countable
file_put_contents($path, "<" . "?php\nreturn " . $data . ";\n");
}
public function add($name, $value)
public function add($name, $value): void
{
if (!$this->isLoaded)
{
@@ -162,13 +157,13 @@ final class Configuration implements \ArrayAccess, \Iterator, \Countable
/**
* Changes readonly params.
* Warning! Developer must use this method very carfully!.
* Warning! Developer must use this method very carefully!
* You must use this method only if you know what you do!
* @param string $name
* @param array $value
* @return void
*/
public function addReadonly($name, $value)
public function addReadonly($name, $value): void
{
if (!$this->isLoaded)
{
@@ -182,7 +177,7 @@ final class Configuration implements \ArrayAccess, \Iterator, \Countable
}
}
public function delete($name)
public function delete($name): void
{
if (!$this->isLoaded)
{
@@ -224,8 +219,7 @@ final class Configuration implements \ArrayAccess, \Iterator, \Countable
return isset($this->data[$offset]);
}
#[\ReturnTypeWillChange]
public function offsetGet($offset)
public function offsetGet($offset): mixed
{
return $this->get($offset);
}
@@ -240,8 +234,7 @@ final class Configuration implements \ArrayAccess, \Iterator, \Countable
$this->delete($offset);
}
#[\ReturnTypeWillChange]
public function current()
public function current(): mixed
{
if (!$this->isLoaded)
{
@@ -253,21 +246,17 @@ final class Configuration implements \ArrayAccess, \Iterator, \Countable
return $c === false ? false : $c["value"];
}
#[\ReturnTypeWillChange]
public function next()
public function next(): void
{
if (!$this->isLoaded)
{
$this->loadConfiguration();
}
$c = next($this->data);
return $c === false ? false : $c["value"];
next($this->data);
}
#[\ReturnTypeWillChange]
public function key()
public function key(): mixed
{
if (!$this->isLoaded)
{
@@ -308,7 +297,7 @@ final class Configuration implements \ArrayAccess, \Iterator, \Countable
return count($this->data);
}
public static function wnc()
public static function wnc(): void
{
Migrator::wnc();
}

View File

@@ -9,6 +9,7 @@ use Bitrix\Main\Data\Cache;
use Bitrix\Main\Data\CacheEngineInterface;
use Bitrix\Main\Data\CacheEngineStatInterface;
use Bitrix\Main\Data\Internal\CacheCleanPathTable;
use Bitrix\Main\Type\DateTime;
abstract class KeyValueEngine implements CacheEngineInterface, CacheEngineStatInterface
{
@@ -151,7 +152,7 @@ abstract class KeyValueEngine implements CacheEngineInterface, CacheEngineStatIn
{
$this->sid = $cacheConfig['sid'];
}
$this->sid .= '|v1';
$this->sid .= '|v1.1';
if (isset($cacheConfig['actual_data']) && !$this->useLock)
{
@@ -289,7 +290,7 @@ abstract class KeyValueEngine implements CacheEngineInterface, CacheEngineStatIn
protected function getPartition($key): string
{
return substr(sha1($key), 0, 2);
return substr(sha1($key), 0, 4);
}
protected function getInitDirKey($baseDirVersion, $baseDir, $initDir): string
@@ -315,7 +316,7 @@ abstract class KeyValueEngine implements CacheEngineInterface, CacheEngineStatIn
* @param bool $create
* @return string
*/
protected function getInitDirVersion($baseDir, $initDir = false, bool $create = true ): string
protected function getInitDirVersion($baseDir, $initDir = false, bool $create = true): string
{
$baseDirVersion = $this->getBaseDirVersion($baseDir);
$initDirHash = sha1($baseDir . '|' . $initDir);
@@ -523,11 +524,11 @@ abstract class KeyValueEngine implements CacheEngineInterface, CacheEngineStatIn
if ($this->useLock)
{
$this->set($key . '~', $this->ttlOld, $initDirVersion);
$cleanFrom = (new \Bitrix\Main\Type\DateTime())->add('+' . $this->ttlOld . ' seconds');
$cleanFrom = (new DateTime())->add('+' . $this->ttlOld . ' seconds');
}
else
{
$cleanFrom = (new \Bitrix\Main\Type\DateTime());
$cleanFrom = (new DateTime());
}
$this->addCleanPath([
@@ -557,7 +558,7 @@ abstract class KeyValueEngine implements CacheEngineInterface, CacheEngineStatIn
{
$this->addCleanPath([
'PREFIX' => $path,
'CLEAN_FROM' => (new \Bitrix\Main\Type\DateTime()),
'CLEAN_FROM' => (new DateTime()),
'CLUSTER_GROUP' => static::$clusterGroup
]);
}
@@ -601,7 +602,7 @@ abstract class KeyValueEngine implements CacheEngineInterface, CacheEngineStatIn
$paths = CacheCleanPathTable::query()
->setSelect(['ID', 'PREFIX'])
->where('CLEAN_FROM', '<=', new \Bitrix\Main\Type\DateTime())
->where('CLEAN_FROM', '<=', new DateTime())
->where('CLUSTER_GROUP', static::$clusterGroup)
->setLimit($count + $delta)
->exec();

View File

@@ -25,8 +25,8 @@ class CacheEngineRedisLight extends CacheEngineRedis
if ($filename <> '')
{
$key = $keyPrefix . '|' . $filename;
$this->delFromSet($initListKey, $filename);
$this->del($key);
$this->delFromSet($initListKey, $filename);
}
elseif ($initDir != '')
{

View File

@@ -22,8 +22,10 @@ class CacheStorage implements StorageInterface
public function read(string $key, int $ttl)
{
$filename = '/' . Cache::getPath($key);
if ($this->cacheEngine->read($value, $this->baseDir, self::CACHE_DIR, $filename, $ttl))
$initDir = $this->getDirname($key);
$filename = $this->getFilename($key);
if ($this->cacheEngine->read($value, $this->baseDir, $initDir, $filename, $ttl))
{
return $value;
}
@@ -33,7 +35,19 @@ class CacheStorage implements StorageInterface
public function write(string $key, $value, int $ttl)
{
$filename = '/' . Cache::getPath($key);
$this->cacheEngine->write($value, $this->baseDir, self::CACHE_DIR, $filename, $ttl);
$initDir = $this->getDirname($key);
$filename = $this->getFilename($key);
$this->cacheEngine->write($value, $this->baseDir, $initDir, $filename, $ttl);
}
private function getDirname(string $key): string
{
return self::CACHE_DIR . '/'. substr(md5($key), 0, 3);
}
private function getFilename(string $key): string
{
return '/' . Cache::getPath($key);
}
}

View File

@@ -1,9 +1,10 @@
<?php
/**
* Bitrix Framework
* @package bitrix
* @subpackage main
* @copyright 2001-2024 Bitrix
* @copyright 2001-2025 Bitrix
*/
namespace Bitrix\Main\Data;
@@ -193,18 +194,18 @@ class TaggedCache
protected function deleteTags(Cache $cache, array $id, array $paths): void
{
CacheTagTable::deleteByFilter(['@ID' => $id]);
CacheTagTable::deleteByFilter(['@RELATIVE_PATH' => array_keys($paths)]);
foreach ($paths as $path => $v)
{
$cache->cleanDir($path);
unset($this->cacheTag[$path]);
}
CacheTagTable::deleteByFilter(['@ID' => $id]);
CacheTagTable::deleteByFilter(['@RELATIVE_PATH' => array_keys($paths)]);
}
public function deleteAllTags(): void
{
CacheTagTable::cleanTable();
}
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Bitrix\Main\DB;
enum Order: string
{
case Asc = 'ASC';
case Desc = 'DESC';
}

View File

@@ -272,22 +272,6 @@ final class ServiceLocator implements ContainerInterface
);
}
if ($this->isInterfaceOrAbstractClass($classNameInParams))
{
if (!isset($this->services[$classNameInParams]))
{
throw new ServiceNotFoundException(
"For {$className} error in params: {$classNameInParams} (abstract or interface) is not registered in ServiceLocator"
);
}
[$classOrClosure] = $this->services[$classNameInParams];
if (is_string($classOrClosure))
{
$classNameInParams = $classOrClosure;
}
}
$paramsForClass[] = $this->get($classNameInParams);
}
@@ -359,11 +343,4 @@ final class ServiceLocator implements ContainerInterface
{
return !isset($this->services[$id]);
}
private function isInterfaceOrAbstractClass(string $className): bool
{
return
interface_exists($className)
|| (class_exists($className) && (new ReflectionClass($className))->isAbstract());
}
}

View File

@@ -219,12 +219,10 @@ class Binder
catch (\TypeError $exception)
{
throw $exception;
// $this->processException($exception);
}
catch (\ErrorException $exception)
{
throw $exception;
// $this->processException($exception);
}
return null;

View File

@@ -17,6 +17,10 @@ use Bitrix\Main\Errorable;
use Bitrix\Main\ArgumentNullException;
use Bitrix\Main\ArgumentTypeException;
use Bitrix\Main\Context;
use Bitrix\Main\Engine\Response\Render\Component;
use Bitrix\Main\Engine\Response\Render\Extension;
use Bitrix\Main\Engine\Response\Render\View;
use Bitrix\Main\Engine\View\ViewPathResolver;
use Bitrix\Main\Event;
use Bitrix\Main\EventManager;
use Bitrix\Main\EventResult;
@@ -108,7 +112,7 @@ class Controller implements Errorable, Controllerable
}
/** @see \Bitrix\Main\Engine\ControllerBuilder::build */
//propbably should refactor with ControllerBuilder::build
//probably should refactor with ControllerBuilder::build
// override parameters
$controller->request = $this->getRequest();
@@ -420,11 +424,12 @@ class Controller implements Errorable, Controllerable
$this->attachFilters($action);
if ($this->prepareParams() &&
$this->processBeforeAction($action) === true &&
$this->triggerOnBeforeAction($action) === true)
{
$result = $action->runWithSourceParametersList();
$result = $this->getActionResponse($action);
if ($action instanceof Errorable)
{
@@ -457,13 +462,18 @@ class Controller implements Errorable, Controllerable
return $result;
}
protected function writeToLogException(\Throwable $e)
protected function getActionResponse(Action $action)
{
return $action->runWithSourceParametersList();
}
protected function writeToLogException(\Throwable $e): void
{
$exceptionHandler = Application::getInstance()->getExceptionHandler();
$exceptionHandler->writeToLog($e);
}
private function processExceptionInDebug(\Throwable $e)
private function processExceptionInDebug(\Throwable $e): void
{
if ($this->shouldWriteToLogException($e))
{
@@ -627,6 +637,7 @@ class Controller implements Errorable, Controllerable
protected function create($actionName)
{
$config = $this->getActionConfig($actionName);
$methodName = $this->generateActionMethodName($actionName);
if (method_exists($this, $methodName))
@@ -646,7 +657,7 @@ class Controller implements Errorable, Controllerable
if (!$config)
{
throw new SystemException(
"Could not find description of {$actionName} in {$this::className()}",
"Could not find description of $actionName in {$this::className()}",
self::EXCEPTION_UNKNOWN_ACTION
);
}
@@ -1085,6 +1096,11 @@ class Controller implements Errorable, Controllerable
return $this->errorCollection->toArray();
}
public function hasErrors(): bool
{
return !$this->errorCollection->isEmpty();
}
/**
* Getting once error with the necessary code.
* @param string $code Code of error.
@@ -1094,4 +1110,115 @@ class Controller implements Errorable, Controllerable
{
return $this->errorCollection->getErrorByCode($code);
}
/**
* Returns response with component content inside site template.
*
* Example:
* ```php
public function fooAction()
{
return $this->renderComponent('bitrix:component.name', 'template-name', [
'param' => 'value',
]);
}
* ```
*
* @see \Bitrix\Main\Engine\Response\Render\Component
*
* @param string $name
* @param string $template
* @param array $params
* @param bool $withSiteTemplate
*
* @return Component
*/
final protected function renderComponent(string $name, string $template = '', array $params = [], bool $withSiteTemplate = true): Component
{
return new Component(
$name,
$template,
$params,
$withSiteTemplate,
);
}
/**
* Render file content.
*
* Example:
* ```php
public function fooAction()
{
return $this->renderView('testfile');
}
* ```
*
* Equivalent to:
* ```php
public function fooAction()
{
return $this->renderView('/local/modules/my.module/views/testfile.php');
}
* ```
*
* Example with params:
* ```php
public function fooAction()
{
return $this->renderView('testfile', [
'id' => 123,
]);
}
* ```
*
* Render only file content, without site template:
* ```php
public function fooAction()
{
return $this->renderView('/local/modules/my.module/views/testfile.php', withSiteTemplate: false);
}
* ```
*
* @see \Bitrix\Main\Engine\Response\Render\View
*
* @param string $viewPath
* @param array $params
* @param bool $withSiteTemplate
*
* @return View
*/
final protected function renderView(string $viewPath, array $params = [], bool $withSiteTemplate = true): View
{
$resolver = new ViewPathResolver(
$viewPath,
'renderView',
);
return new View(
$resolver->resolve(),
$params,
$withSiteTemplate
);
}
/**
* Render extension. It's not SSR!
*
* @see \Bitrix\Main\Engine\Response\Render\Extension
*
* @param string $extension
* @param array $params
* @param bool $withSiteTemplate
*
* @return Extension
*/
final protected function renderExtension(string $extension, array $params = [], bool $withSiteTemplate = true): Extension
{
return new Extension(
$extension,
$params,
$withSiteTemplate,
);
}
}

View File

@@ -1,4 +1,5 @@
<?php
namespace Bitrix\Main\Engine;
@@ -12,6 +13,7 @@ final class ControllerBuilder
{
$scope = $options['scope'] ?? Controller::SCOPE_AJAX;
$currentUser = $options['currentUser'] ?? CurrentUser::get();
$request = $options['request'] ?? null;
$reflectionClass = new \ReflectionClass($controllerClass);
if ($reflectionClass->isAbstract())
@@ -27,7 +29,7 @@ final class ControllerBuilder
/** @var Controller $controller */
/** @see \Bitrix\Main\Engine\Controller::__construct */
/** @see \Bitrix\Main\Engine\Controller::forward */
$controller = $reflectionClass->newInstance();
$controller = $reflectionClass->newInstance($request);
$controller->setScope($scope);
$controller->setCurrentUser($currentUser);
@@ -38,4 +40,4 @@ final class ControllerBuilder
throw new ObjectException("Unable to construct controller {{$controllerClass}}.", $exception);
}
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Bitrix\Main\Engine\Response\Render;
use Bitrix\Main\Application;
use Bitrix\Main\HttpResponse;
use CMain;
abstract class Base extends HttpResponse
{
abstract protected function renderContent(): void;
protected function __construct(
bool $withSiteTemplate,
)
{
parent::__construct();
$this->fillContent($withSiteTemplate);
}
final protected function fillContent(bool $withSiteTemplate): void
{
global $APPLICATION;
/**
* @var CMain $APPLICATION
*/
$APPLICATION->RestartBuffer();
if ($withSiteTemplate)
{
$this->includeHeader();
}
else
{
$APPLICATION->ShowAjaxHead();
}
$this->renderContent();
if ($withSiteTemplate)
{
$this->includeFooter();
}
$content = $APPLICATION->EndBufferContentMan();
$this->setContent($content);
}
final protected function includeHeader(): void
{
require Application::getDocumentRoot() . '/bitrix/modules/main/include/prolog_after.php';
}
final protected function includeFooter(): void
{
require Application::getDocumentRoot() . '/bitrix/modules/main/include/epilog_before.php';
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Bitrix\Main\Engine\Response\Render;
use CMain;
/**
* Response with component on site template.
*
* @see \Bitrix\Main\Engine\Response\Component for AJAX responses.
*/
final class Component extends Base
{
public function __construct(
private string $name,
private string $template,
private array $params = [],
bool $withSiteTemplate = true,
)
{
parent::__construct($withSiteTemplate);
}
protected function renderContent(): void
{
global $APPLICATION;
/**
* @var CMain $APPLICATION
*/
$APPLICATION->IncludeComponent(
$this->name,
$this->template,
$this->params
);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Bitrix\Main\Engine\Response\Render\Exception;
final class InvalidConfigExtensionException extends RenderException
{
public function __construct(string $extension, string $reason)
{
parent::__construct(
"Invalid config format for extension `{$extension}`: {$reason}",
);
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Bitrix\Main\Engine\Response\Render\Exception;
final class NotFoundPathToViewException extends RenderException
{
/**
* @param string $pathOnDocumentRoot
*/
public function __construct(string $pathOnDocumentRoot)
{
parent::__construct(
"Path to view `$pathOnDocumentRoot` not found on document root.",
);
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace Bitrix\Main\Engine\Response\Render\Exception;
use Bitrix\Main\SystemException;
abstract class RenderException extends SystemException
{}

View File

@@ -0,0 +1,68 @@
<?php
namespace Bitrix\Main\Engine\Response\Render;
use Bitrix\Main\Engine\Response\Render\Exception\InvalidConfigExtensionException;
use Bitrix\Main\Web\Json;
use CUtil;
final class Extension extends Base
{
public function __construct(
private string $extension,
private array $params = [],
bool $withSiteTemplate = true,
)
{
parent::__construct($withSiteTemplate);
}
protected function renderContent(): void
{
$html = null;
$controllerEntryPoint = $this->getControllerEntryPoint();
if ($controllerEntryPoint)
{
$containerId = uniqid('render_container_');
$selector = CUtil::JSEscape('#' . $containerId);
$jsonParams = Json::encode($this->params);
$html = join('', [
"<div id='{$containerId}'></div>",
"<script>BX.ready(() => { {$controllerEntryPoint}('{$selector}', {$jsonParams}) });</script>",
]);
}
else
{
throw new InvalidConfigExtensionException(
$this->extension,
'`controllerEntrypoint` is not defined in extension config',
);
}
\Bitrix\Main\UI\Extension::load($this->extension);
echo $html;
}
private function getControllerEntryPoint(): ?string
{
$config = \Bitrix\Main\UI\Extension::getConfig($this->extension);
if (isset($config['controllerEntrypoint']))
{
if (!is_string($config['controllerEntrypoint']))
{
throw new InvalidConfigExtensionException(
$this->extension,
'`controllerEntrypoint` in config must be a string with JS function name',
);
}
return $config['controllerEntrypoint'];
}
return null;
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Bitrix\Main\Engine\Response\Render;
use CMain;
/**
* Response with static view content.
*
* @see \Bitrix\Main\Routing\Controllers\PublicPageController for using static page on router.
*/
final class View extends Base
{
public function __construct(
private string $pathOnDocumentRoot,
private array $params = [],
bool $withSiteTemplate = true,
)
{
parent::__construct($withSiteTemplate);
}
protected function renderContent(): void
{
global $APPLICATION;
/**
* @var CMain $APPLICATION
*/
$APPLICATION->IncludeFile(
$this->pathOnDocumentRoot,
$this->params,
[],
);
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Bitrix\Main\Engine\View\Exception;
use Bitrix\Main\SystemException;
final class FileNotExistsException extends SystemException
{
public function __construct(string $path)
{
parent::__construct(
"File `{$path}` not exists"
);
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Bitrix\Main\Engine\View\Exception;
use Bitrix\Main\SystemException;
final class InvalidPathOnViewException extends SystemException
{
public function __construct(string $path)
{
parent::__construct(
"Invalid path to view file: {$path}"
);
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Bitrix\Main\Engine\View\Exception;
use Bitrix\Main\SystemException;
final class PathOutsideDocumentRootException extends SystemException
{
public function __construct(string $path)
{
parent::__construct(
"Path `{$path}` is outside the document root"
);
}
}

View File

@@ -0,0 +1,122 @@
<?php
namespace Bitrix\Main\Engine\View;
use Bitrix\Main\Diag\Helper;
use Bitrix\Main\Engine\View\Exception\FileNotExistsException;
use Bitrix\Main\Engine\View\Exception\InvalidPathOnViewException;
use Bitrix\Main\Engine\View\Exception\PathOutsideDocumentRootException;
use Bitrix\Main\IO\Path;
use Bitrix\Main\Loader;
use Bitrix\Main\SystemException;
final class ViewPathResolver
{
private string $documentRoot;
public function __construct(
private string $viewPath,
private string $renderFunctionName,
)
{
$this->documentRoot = Loader::getDocumentRoot();
}
/**
* Gets path on document root to view file.
*
* @return string
*/
public function resolve(): string
{
$path = $this->getAbsolutePath();
if (!empty($path))
{
$path = Path::normalize($path);
}
if (is_null($path))
{
throw new InvalidPathOnViewException($this->viewPath);
}
elseif (!file_exists($path))
{
throw new FileNotExistsException($path);
}
elseif (!str_starts_with($path, $this->documentRoot))
{
throw new PathOutsideDocumentRootException($this->viewPath);
}
$pathOnDocumentRoot = substr(
$path,
strlen($this->documentRoot)
);
return $pathOnDocumentRoot;
}
private function getAbsolutePath(): ?string
{
if (str_starts_with($this->viewPath, '/'))
{
return $this->documentRoot . $this->viewPath;
}
return $this->getPathToViewOnModule();
}
private function getPathToViewOnModule(): ?string
{
$moduleId = $this->getModuleId();
$postfix = '';
$extension = pathinfo($this->viewPath, PATHINFO_EXTENSION);
if ($extension === '')
{
$postfix = '.php';
}
elseif ($extension !== 'php')
{
throw new SystemException('Invalid extension for view file: ' . $extension);
}
$absolutePath = Loader::getLocal(
'modules/' . $moduleId . '/views/' . $this->viewPath . $postfix,
);
if ($absolutePath !== false)
{
return $absolutePath;
}
return null;
}
private function getModuleId(): string
{
$trace = Helper::getBackTrace();
foreach ($trace as $item)
{
if ($item['function'] === $this->renderFunctionName)
{
$path = Path::normalize($item['file']);
$parts = explode('/', $path);
$prevPart = null;
foreach ($parts as $part)
{
if ($part === 'lib')
{
if (isset($prevPart))
{
return $prevPart;
}
}
$prevPart = $part;
}
}
}
throw new SystemException('Cannot parse module id from path');
}
}

View File

@@ -3,7 +3,7 @@
* Bitrix Framework
* @package bitrix
* @subpackage main
* @copyright 2001-2019 Bitrix
* @copyright 2001-2025 Bitrix
*/
namespace Bitrix\Main\EventLog\Internal;
@@ -38,8 +38,9 @@ class EventLogTable extends Data\DataManager
{
return [
(new Fields\IntegerField("ID"))
->configurePrimary(true)
->configureAutocomplete(true),
->configurePrimary()
->configureAutocomplete()
->configureSize(8),
(new Fields\DatetimeField("TIMESTAMP_X")),
(new Fields\StringField("SEVERITY")),
(new Fields\StringField("AUDIT_TYPE_ID")),

View File

@@ -74,6 +74,8 @@ abstract class EntityDataProvider extends DataProvider
'intranetUsersOnly' => true,
'fieldName' => $params['fieldName'],
'referenceClass' => ($params['referenceClass'] ?? null),
'referenceFieldName' => ($params['referenceFieldName'] ?? null),
'referenceAdditionalFilter' => ($params['referenceAdditionalFilter'] ?? null),
'entityTypeId' => ($params['entityTypeId'] ?? null),
'module' => ($params['module'] ?? null),
]

View File

@@ -58,7 +58,7 @@ class HttpRequest extends Request
* @param array $files _FILES
* @param array $cookies _COOKIE
*/
public function __construct(Server $server, array $queryString, array $postData, array $files, array $cookies)
public function __construct(Server $server, array $queryString, array $postData, array $files, array $cookies, array $jsonData = [])
{
$request = array_merge($queryString, $postData);
parent::__construct($server, $request);
@@ -69,7 +69,7 @@ class HttpRequest extends Request
$this->cookiesRaw = new Type\ParameterDictionary($cookies);
$this->cookies = new Type\ParameterDictionary($this->prepareCookie($cookies));
$this->headers = $this->buildHttpHeaders($server);
$this->jsonData = new Type\ParameterDictionary();
$this->jsonData = new Type\ParameterDictionary($jsonData);
}
private function buildHttpHeaders(Server $server)
@@ -594,4 +594,22 @@ class HttpRequest extends Request
}
}
}
public function decodeJsonStrict(): void
{
if (!$this->isJson())
{
throw new SystemException('Input is not valid JSON');
}
if (empty($this->jsonData->getValues()))
{
$json = Web\Json::decode(static::getInput());
if (!is_array($json))
{
throw new SystemException('Decoded JSON is not an array');
}
$this->jsonData = new Type\ParameterDictionary($json);
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -2,6 +2,7 @@
namespace Bitrix\Main;
use Bitrix\Main\Config\Configuration;
use Bitrix\Main\DI\ServiceLocator;
/**
@@ -49,6 +50,8 @@ class Loader
*/
protected static $namespaces = [];
protected static $autoLoadClasses = [];
protected static $aliases = [];
protected static $classAliases = [];
/**
* @var bool Controls throwing exception by requireModule method
*/
@@ -290,6 +293,26 @@ class Loader
}
}
/**
* Registers class aliases for autoloading.
*
* @param array $aliases Array with aliases as keys and classes as values.
*/
public static function registerClassAliases(array $aliases): void
{
foreach ($aliases as $alias => $class)
{
$alias = strtolower(ltrim($alias, "\\"));
$class = strtolower(ltrim($class, "\\"));
// one class for an alias
self::$aliases[$alias] = $class;
// but many aliases for a class
self::$classAliases[$class][] = $alias;
}
}
/**
* Registers namespaces with custom paths.
* e.g. ('Bitrix\Main\Dev', '/home/bitrix/web/site/bitrix/modules/main/dev/lib')
@@ -345,15 +368,24 @@ class Loader
$classLower = strtolower($className);
// dynamically define the alias for a class
if (isset(self::$aliases[$classLower]))
{
class_alias(self::$aliases[$classLower], $classLower);
return;
}
static $documentRoot = null;
if ($documentRoot === null)
{
$documentRoot = self::getDocumentRoot();
}
//optimization via direct paths
if (isset(self::$autoLoadClasses[$classLower]))
{
// optimization via direct paths
$pathInfo = self::$autoLoadClasses[$classLower];
if ($pathInfo["module"] != "")
{
@@ -372,81 +404,93 @@ class Loader
{
require_once($documentRoot . $pathInfo["file"]);
}
return;
}
if (preg_match("#[^\\\\/a-zA-Z0-9_]#", $className))
else
{
return;
}
// search the path in namespaces
$tryFiles = [[
"real" => $className,
"lower" => $classLower,
]];
if (str_ends_with($classLower, "table"))
{
// old *Table stored in reserved files
$tryFiles[] = [
"real" => substr($className, 0, -5),
"lower" => substr($classLower, 0, -5),
];
}
foreach ($tryFiles as $classInfo)
{
$classParts = explode("\\", $classInfo["lower"]);
//remove class name
array_pop($classParts);
while (!empty($classParts))
if (preg_match("#[^\\\\/a-zA-Z0-9_]#", $className))
{
//go from the end
$namespace = implode("\\", $classParts) . "\\";
return;
}
if (isset(self::$namespaces[$namespace]))
$tryFiles = [[
"real" => $className,
"lower" => $classLower,
]];
if (str_ends_with($classLower, "table"))
{
// old *Table stored in reserved files
$tryFiles[] = [
"real" => substr($className, 0, -5),
"lower" => substr($classLower, 0, -5),
];
}
foreach ($tryFiles as $classInfo)
{
$classParts = explode("\\", $classInfo["lower"]);
//remove class name
array_pop($classParts);
while (!empty($classParts))
{
//found
foreach (self::$namespaces[$namespace] as $namespaceLocation)
//go from the end
$namespace = implode("\\", $classParts) . "\\";
if (isset(self::$namespaces[$namespace]))
{
$depth = $namespaceLocation["depth"];
$path = $namespaceLocation["path"];
$fileParts = explode("\\", $classInfo["real"]);
for ($i = 0; $i <= $depth; $i++)
//found
foreach (self::$namespaces[$namespace] as $namespaceLocation)
{
array_shift($fileParts);
}
$depth = $namespaceLocation["depth"];
$path = $namespaceLocation["path"];
$classPath = implode("/", $fileParts);
$fileParts = explode("\\", $classInfo["real"]);
$classPathLower = strtolower($classPath);
for ($i = 0; $i <= $depth; $i++)
{
array_shift($fileParts);
}
// final path lower case
$filePath = $path . '/' . $classPathLower . ".php";
$classPath = implode("/", $fileParts);
if (file_exists($filePath))
{
require_once($filePath);
break 3;
}
$classPathLower = strtolower($classPath);
// final path original case
$filePath = $path . '/' . $classPath . ".php";
// final path lower case
$filePath = $path . '/' . $classPathLower . ".php";
if (file_exists($filePath))
{
require_once($filePath);
break 3;
if (file_exists($filePath))
{
require_once($filePath);
break 3;
}
// final path original case
$filePath = $path . '/' . $classPath . ".php";
if (file_exists($filePath))
{
require_once($filePath);
break 3;
}
}
}
}
//try the shorter namespace
array_pop($classParts);
//try the shorter namespace
array_pop($classParts);
}
}
}
// dynamically define the class aliases
if (isset(self::$classAliases[$classLower]))
{
foreach (self::$classAliases[$classLower] as $alias)
{
class_exists($alias);
}
}
}
@@ -575,6 +619,40 @@ class Loader
{
self::$requireThrowException = (bool)$requireThrowException;
}
/**
* Include autoload.php files from composer folder.
*
* @return void
*/
public static function includeComposerAutoload(): void
{
// load from config
$composerSettings = Configuration::getValue('composer');
if (empty($composerSettings['config_path']))
{
return;
}
$composerFilePath = (string)$composerSettings['config_path'];
if ($composerFilePath[0] !== '/')
{
$composerFilePath = realpath(
self::getDocumentRoot() . '/' . $composerFilePath
);
if (empty($composerFilePath))
{
return;
}
}
require_once dirname($composerFilePath) . '/vendor/autoload.php';
}
public static function getNamespaces(): array
{
return self::$namespaces;
}
}
class LoaderException extends \Exception

View File

@@ -1,16 +1,18 @@
<?php
/**
* Bitrix Framework
* @package bitrix
* @subpackage main
* @copyright 2001-2012 Bitrix
* @copyright 2001-2025 Bitrix
*/
namespace Bitrix\Main\Mail;
use Bitrix\Main;
use Bitrix\Main\Mail\Internal as MailInternal;
use Bitrix\Main\Config as Config;
use Bitrix\Main\Config;
use Bitrix\Main\Application;
use Bitrix\Main\ORM\Data\AddResult;
class Event
{
@@ -27,6 +29,11 @@ class Event
*/
public static function sendImmediate(array $data)
{
if (!static::onBeforeEventAdd($data))
{
return static::SEND_RESULT_NONE;
}
$data["ID"] = 0;
return static::handleEvent($data);
@@ -36,21 +43,28 @@ class Event
* Send mail event
*
* @param array $data Params of event
* @return Main\Entity\AddResult
* @return AddResult
*/
public static function send(array $data)
{
if (!static::onBeforeEventAdd($data))
{
return (new AddResult())->addError(new Main\Error('OnBeforeEventAdd'));
}
$manageCache = Application::getInstance()->getManagedCache();
if(CACHED_b_event !== false && $manageCache->read(CACHED_b_event, "events"))
if (CACHED_b_event !== false && $manageCache->read(CACHED_b_event, "events"))
{
$manageCache->clean('events');
}
$fileList = [];
if(isset($data['FILE']))
if (isset($data['FILE']))
{
if(is_array($data['FILE']))
if (is_array($data['FILE']))
{
$fileList = $data['FILE'];
}
unset($data['FILE']);
}
@@ -79,7 +93,7 @@ class Event
$connection->startTransaction();
$result = MailInternal\EventTable::add($data);
$result = Internal\EventTable::add($data);
if ($result->isSuccess())
{
@@ -98,7 +112,7 @@ class Event
$attachment['FILE_ID'] = \CFile::SaveFile($file['FILE'], 'main');
}
MailInternal\EventAttachmentTable::add($attachment);
Internal\EventAttachmentTable::add($attachment);
}
}
@@ -107,38 +121,76 @@ class Event
return $result;
}
protected static function onBeforeEventAdd(array &$data): bool
{
foreach (GetModuleEvents("main", "OnBeforeEventAdd", true) as $arEvent)
{
if (ExecuteModuleEventEx($arEvent, [&$data["EVENT_NAME"], &$data["LID"], &$data["C_FIELDS"], &$data["MESSAGE_ID"], &$data["FILE"], &$data["LANGUAGE_ID"]]) === false)
{
return false;
}
}
if (isset($data['LID']) && is_array($data['LID']))
{
$data['LID'] = implode(",", $data['LID']);
}
if (!is_array($data["C_FIELDS"] ?? null))
{
$data["C_FIELDS"] = [];
}
if (isset($data["MESSAGE_ID"]) && (int)$data["MESSAGE_ID"] > 0)
{
$data["MESSAGE_ID"] = (int)$data["MESSAGE_ID"];
}
else
{
unset($data["MESSAGE_ID"]);
}
return true;
}
/**
* @param array $arEvent
* @return string
* @throws Main\ArgumentException
* @throws Main\ArgumentNullException
* @throws Main\ArgumentTypeException
*/
public static function handleEvent(array $arEvent)
{
if(!isset($arEvent['FIELDS']) && isset($arEvent['C_FIELDS']))
if (!isset($arEvent['FIELDS']) && isset($arEvent['C_FIELDS']))
{
$arEvent['FIELDS'] = $arEvent['C_FIELDS'];
}
if(!is_array($arEvent['FIELDS']))
throw new Main\ArgumentTypeException("FIELDS" );
if (!is_array($arEvent['FIELDS']))
{
throw new Main\ArgumentTypeException("FIELDS");
}
$flag = static::SEND_RESULT_TEMPLATE_NOT_FOUND; // no templates
$arResult = array(
$arResult = [
"Success" => false,
"Fail" => false,
"Was" => false,
"Skip" => false,
);
];
$trackRead = null;
$trackClick = null;
if(array_key_exists('TRACK_READ', $arEvent))
if (array_key_exists('TRACK_READ', $arEvent))
{
$trackRead = $arEvent['TRACK_READ'];
if(array_key_exists('TRACK_CLICK', $arEvent))
}
if (array_key_exists('TRACK_CLICK', $arEvent))
{
$trackClick = $arEvent['TRACK_CLICK'];
}
$arSites = explode(",", $arEvent["LID"]);
if(empty($arSites))
if (empty($arSites))
{
return $flag;
}
@@ -148,76 +200,76 @@ class Event
$charset = false;
$serverName = null;
static $sites = array();
static $sites = [];
$infoSite = reset($arSites);
if(!isset($sites[$infoSite]))
if (!isset($sites[$infoSite]))
{
$siteDb = Main\SiteTable::getList(array(
'select' => array('SERVER_NAME', 'CULTURE_CHARSET'=>'CULTURE.CHARSET'),
'filter' => array('=LID' => $infoSite)
));
$siteDb = Main\SiteTable::getList([
'select' => ['SERVER_NAME', 'CULTURE_CHARSET' => 'CULTURE.CHARSET'],
'filter' => ['=LID' => $infoSite],
]);
$sites[$infoSite] = $siteDb->fetch();
}
if(is_array($sites[$infoSite]))
if (is_array($sites[$infoSite]))
{
$charset = $sites[$infoSite]['CULTURE_CHARSET'];
$serverName = $sites[$infoSite]['SERVER_NAME'];
}
if(!$charset)
if (!$charset)
{
return $flag;
}
// get filter for list of message templates
$arEventMessageFilter = array();
$MESSAGE_ID = intval($arEvent["MESSAGE_ID"]);
if($MESSAGE_ID > 0)
$arEventMessageFilter = [];
$MESSAGE_ID = intval($arEvent["MESSAGE_ID"] ?? 0);
if ($MESSAGE_ID > 0)
{
$eventMessageDb = MailInternal\EventMessageTable::getById($MESSAGE_ID);
if($eventMessageDb->Fetch())
$eventMessageDb = Internal\EventMessageTable::getById($MESSAGE_ID);
if ($eventMessageDb->Fetch())
{
$arEventMessageFilter['=ID'] = $MESSAGE_ID;
$arEventMessageFilter['=ACTIVE'] = 'Y';
}
}
if(empty($arEventMessageFilter))
if (empty($arEventMessageFilter))
{
$arEventMessageFilter = array(
$arEventMessageFilter = [
'=ACTIVE' => 'Y',
'=EVENT_NAME' => $arEvent["EVENT_NAME"],
'=EVENT_MESSAGE_SITE.SITE_ID' => $arSites,
);
];
if($arEvent["LANGUAGE_ID"] <> '')
if ($arEvent["LANGUAGE_ID"] <> '')
{
$arEventMessageFilter[] = array(
$arEventMessageFilter[] = [
"LOGIC" => "OR",
array("=LANGUAGE_ID" => $arEvent["LANGUAGE_ID"]),
array("=LANGUAGE_ID" => null),
);
["=LANGUAGE_ID" => $arEvent["LANGUAGE_ID"]],
["=LANGUAGE_ID" => null],
];
}
}
// get list of message templates of event
$messageDb = MailInternal\EventMessageTable::getList(array(
'select' => array('ID'),
$messageDb = Internal\EventMessageTable::getList([
'select' => ['ID'],
'filter' => $arEventMessageFilter,
'group' => array('ID')
));
'group' => ['ID'],
]);
while($arMessage = $messageDb->fetch())
while ($arMessage = $messageDb->fetch())
{
$eventMessage = MailInternal\EventMessageTable::getRowById($arMessage['ID']);
$eventMessage = Internal\EventMessageTable::getRowById($arMessage['ID']);
$eventMessage['FILE'] = array();
$attachmentDb = MailInternal\EventMessageAttachmentTable::getList(array(
'select' => array('FILE_ID'),
'filter' => array('=EVENT_MESSAGE_ID' => $arMessage['ID']),
));
while($arAttachmentDb = $attachmentDb->fetch())
$eventMessage['FILE'] = [];
$attachmentDb = Internal\EventMessageAttachmentTable::getList([
'select' => ['FILE_ID'],
'filter' => ['=EVENT_MESSAGE_ID' => $arMessage['ID']],
]);
while ($arAttachmentDb = $attachmentDb->fetch())
{
$eventMessage['FILE'][] = $arAttachmentDb['FILE_ID'];
}
@@ -227,26 +279,26 @@ class Event
foreach (GetModuleEvents("main", "OnBeforeEventSend", true) as $event)
{
if(ExecuteModuleEventEx($event, array(&$arFields, &$eventMessage, $context, &$arResult)) === false)
if (ExecuteModuleEventEx($event, [&$arFields, &$eventMessage, $context, &$arResult]) === false)
{
continue 2;
}
}
// get message object for send mail
$arMessageParams = array(
$arMessageParams = [
'EVENT' => $arEvent,
'FIELDS' => $arFields,
'MESSAGE' => $eventMessage,
'SITE' => $arSites,
'CHARSET' => $charset,
);
];
$message = EventMessageCompiler::createInstance($arMessageParams);
try
{
$message->compile();
}
catch(StopException $e)
catch (StopException)
{
$arResult["Was"] = true;
$arResult["Fail"] = true;
@@ -254,7 +306,7 @@ class Event
}
// send mail
$result = Main\Mail\Mail::send(array(
$result = Main\Mail\Mail::send([
'TO' => $message->getMailTo(),
'SUBJECT' => $message->getMailSubject(),
'BODY' => $message->getMailBody(),
@@ -265,36 +317,50 @@ class Event
'ATTACHMENT' => $message->getMailAttachment(),
'TRACK_READ' => $trackRead,
'TRACK_CLICK' => $trackClick,
'LINK_PROTOCOL' => Config\Option::get("main", "mail_link_protocol", ''),
'LINK_PROTOCOL' => Config\Option::get("main", "mail_link_protocol"),
'LINK_DOMAIN' => $serverName,
'CONTEXT' => $context,
));
if($result)
]);
if ($result)
{
$arResult["Success"] = true;
}
else
{
$arResult["Fail"] = true;
}
$arResult["Was"] = true;
}
if($arResult["Was"])
if ($arResult["Was"])
{
if($arResult["Success"])
if ($arResult["Success"])
{
if($arResult["Fail"])
$flag = static::SEND_RESULT_PARTLY; // partly sent
if ($arResult["Fail"])
{
// partly sent
$flag = static::SEND_RESULT_PARTLY;
}
else
$flag = static::SEND_RESULT_SUCCESS; // all sent
{
// all sent
$flag = static::SEND_RESULT_SUCCESS;
}
}
else
{
if($arResult["Fail"])
$flag = static::SEND_RESULT_ERROR; // all templates failed
if ($arResult["Fail"])
{
// all templates failed
$flag = static::SEND_RESULT_ERROR;
}
}
}
elseif($arResult["Skip"])
elseif ($arResult["Skip"])
{
$flag = static::SEND_RESULT_NONE; // skip this event
// skip this event
$flag = static::SEND_RESULT_NONE;
}
return $flag;

View File

@@ -3,8 +3,8 @@
namespace Bitrix\Main\Mail\Internal;
use Bitrix\Main\Application;
use Bitrix\Main\Entity;
use Bitrix\Main\Type\DateTime;
use Bitrix\Main\ORM\Data\DataManager;
/**
* Class BlacklistTable
@@ -24,7 +24,7 @@ use Bitrix\Main\Type\DateTime;
* @method static \Bitrix\Main\Mail\Internal\EO_Blacklist wakeUpObject($row)
* @method static \Bitrix\Main\Mail\Internal\EO_Blacklist_Collection wakeUpCollection($rows)
*/
class BlacklistTable extends Entity\DataManager
class BlacklistTable extends DataManager
{
const CategoryAuto = 0;
const CategoryManual = 1;

View File

@@ -1,15 +1,18 @@
<?php
/**
* Bitrix Framework
* @package bitrix
* @subpackage main
* @copyright 2001-2012 Bitrix
* @copyright 2001-2025 Bitrix
*/
namespace Bitrix\Main\Mail\Internal;
use Bitrix\Main\Entity;
use Bitrix\Main\ORM\Fields\ArrayField;
use Bitrix\Main\Type as Type;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\StringField;
/**
* Class EventTable
@@ -27,7 +30,7 @@ use Bitrix\Main\Type as Type;
* @method static \Bitrix\Main\Mail\Internal\EO_Event wakeUpObject($row)
* @method static \Bitrix\Main\Mail\Internal\EO_Event_Collection wakeUpCollection($rows)
*/
class EventTable extends Entity\DataManager
class EventTable extends DataManager
{
/**
* @return string
@@ -107,7 +110,7 @@ class EventTable extends Entity\DataManager
{
return array(
array(__CLASS__, "getFetchModificationForFieldsField"),
array(new Entity\StringField('FIELDS', array()), "unserialize")
array(new StringField('FIELDS', array()), "unserialize")
);
}
@@ -198,7 +201,7 @@ class EventTable extends Entity\DataManager
$newar[$key] = $val;
}
$field = new Entity\StringField('FIELDS', array());
$field = new StringField('FIELDS', array());
return $field->serialize($newar);
}
}

View File

@@ -3,12 +3,12 @@
* Bitrix Framework
* @package bitrix
* @subpackage main
* @copyright 2001-2012 Bitrix
* @copyright 2001-2025 Bitrix
*/
namespace Bitrix\Main\Mail\Internal;
use Bitrix\Main\Entity;
use Bitrix\Main\ORM\Data\DataManager;
/**
* Class EventAttachmentTable
@@ -26,7 +26,7 @@ use Bitrix\Main\Entity;
* @method static \Bitrix\Main\Mail\Internal\EO_EventAttachment wakeUpObject($row)
* @method static \Bitrix\Main\Mail\Internal\EO_EventAttachment_Collection wakeUpCollection($rows)
*/
class EventAttachmentTable extends Entity\DataManager
class EventAttachmentTable extends DataManager
{
/**

View File

@@ -1,16 +1,18 @@
<?php
/**
* Bitrix Framework
* @package bitrix
* @subpackage main
* @copyright 2001-2012 Bitrix
* @copyright 2001-2025 Bitrix
*/
namespace Bitrix\Main\Mail\Internal;
use Bitrix\Main\Orm;
use Bitrix\Main\Entity;
use Bitrix\Main\Type as Type;
use Bitrix\Main\Type;
use Bitrix\Main\ORM;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\ArrayField;
/**
* Class EventMessageTable
@@ -28,7 +30,7 @@ use Bitrix\Main\Type as Type;
* @method static \Bitrix\Main\Mail\Internal\EO_EventMessage wakeUpObject($row)
* @method static \Bitrix\Main\Mail\Internal\EO_EventMessage_Collection wakeUpCollection($rows)
*/
class EventMessageTable extends Entity\DataManager
class EventMessageTable extends DataManager
{
/**
* @return string
@@ -52,7 +54,9 @@ class EventMessageTable extends Entity\DataManager
'TIMESTAMP_X' => array(
'data_type' => 'datetime',
'required' => true,
'default_value' => function(){return new Type\DateTime();},
'default_value' => function() {
return new Type\DateTime();
},
),
'EVENT_NAME' => array(
'data_type' => 'string',
@@ -120,7 +124,7 @@ class EventMessageTable extends Entity\DataManager
'SITE_TEMPLATE_ID' => array(
'data_type' => 'string',
),
(new Orm\Fields\ArrayField('ADDITIONAL_FIELD'))->configureSerializationPhp(),
(new ArrayField('ADDITIONAL_FIELD'))->configureSerializationPhp(),
'EVENT_MESSAGE_SITE' => array(
'data_type' => 'Bitrix\Main\Mail\Internal\EventMessageSite',
'reference' => array('=this.ID' => 'ref.EVENT_MESSAGE_ID'),
@@ -217,19 +221,19 @@ class EventMessageTable extends Entity\DataManager
if(!empty($arReplaceTagsOne))
$strResult = str_replace(array_keys($arReplaceTagsOne), array_values($arReplaceTagsOne), $strResult);
// php parser delete newline folowing the closing tag in string passed to eval
// php parser delete newline following the closing tag in string passed to eval
$strResult = str_replace(array("?>\n", "?>\r\n"), array("?>\n\n", "?>\r\n\r\n"), $strResult);
return $strResult;
}
/**
* @param Entity\Event $event
* @return Entity\EventResult
* @param ORM\Event $event
* @return ORM\EventResult
*/
public static function onBeforeUpdate(Entity\Event $event)
public static function onBeforeUpdate(ORM\Event $event)
{
$result = new Entity\EventResult;
$result = new ORM\EventResult();
$data = $event->getParameters();
if(array_key_exists('MESSAGE', $data['fields']))
@@ -242,12 +246,12 @@ class EventMessageTable extends Entity\DataManager
}
/**
* @param Entity\Event $event
* @return Entity\EventResult
* @param ORM\Event $event
* @return ORM\EventResult
*/
public static function onBeforeAdd(Entity\Event $event)
public static function onBeforeAdd(ORM\Event $event)
{
$result = new Entity\EventResult;
$result = new ORM\EventResult();
$data = $event->getParameters();
if(array_key_exists('MESSAGE', $data['fields']))

View File

@@ -1,4 +1,5 @@
<?php
/**
* Bitrix Framework
* @package bitrix
@@ -8,7 +9,7 @@
namespace Bitrix\Main\Mail\Internal;
use Bitrix\Main\Entity;
use Bitrix\Main\ORM\Data\DataManager;
/**
* Class EventMessageAttachmentTable
@@ -26,7 +27,7 @@ use Bitrix\Main\Entity;
* @method static \Bitrix\Main\Mail\Internal\EO_EventMessageAttachment wakeUpObject($row)
* @method static \Bitrix\Main\Mail\Internal\EO_EventMessageAttachment_Collection wakeUpCollection($rows)
*/
class EventMessageAttachmentTable extends Entity\DataManager
class EventMessageAttachmentTable extends DataManager
{
/**
* @return string
@@ -53,5 +54,4 @@ class EventMessageAttachmentTable extends Entity\DataManager
),
);
}
}

View File

@@ -7,7 +7,7 @@
*/
namespace Bitrix\Main\Mail\Internal;
use Bitrix\Main\Entity;
use Bitrix\Main\ORM\Data\DataManager;
/**
* Class SenderSendCounterTable
@@ -25,7 +25,7 @@ use Bitrix\Main\Entity;
* @method static \Bitrix\Main\Mail\Internal\EO_SenderSendCounter wakeUpObject($row)
* @method static \Bitrix\Main\Mail\Internal\EO_SenderSendCounter_Collection wakeUpCollection($rows)
*/
class SenderSendCounterTable extends Entity\DataManager
class SenderSendCounterTable extends DataManager
{
public static function getTableName()
{

View File

@@ -2,11 +2,11 @@
namespace Bitrix\Main\Mail\Internal;
use Bitrix\Main\Entity;
use Bitrix\Main\Config;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\Security;
use Bitrix\Main\ORM\Fields;
use Bitrix\Main\ORM\Data\DataManager;
/**
* Class SenderTable
@@ -25,9 +25,8 @@ use Bitrix\Main\ORM\Fields;
* @method static \Bitrix\Main\Mail\Internal\Sender wakeUpObject($row)
* @method static \Bitrix\Main\Mail\Internal\EO_Sender_Collection wakeUpCollection($rows)
*/
class SenderTable extends Entity\DataManager
class SenderTable extends DataManager
{
public static function getTableName()
{
return 'b_main_mail_sender';
@@ -42,7 +41,6 @@ class SenderTable extends Entity\DataManager
return $result;
}
public static function getObjectClass()
{
return Sender::class;
@@ -163,5 +161,4 @@ class SenderTable extends Entity\DataManager
->configureNullable(),
];
}
}

View File

@@ -9,6 +9,8 @@
namespace Bitrix\Main\Mail;
use Bitrix\Main\Type;
use Bitrix\Main\ORM\Fields\ExpressionField;
use Bitrix\Main\DB\SqlExpression;
class SenderSendCounter
{
@@ -50,9 +52,9 @@ class SenderSendCounter
$counter = 0;
$date = new Type\Date(date("01.m.Y"), "d.m.Y");
$res = Internals\SenderSendCounterTable::getList(array(
$res = Internal\SenderSendCounterTable::getList(array(
"select" => array(
new \Bitrix\Main\Entity\ExpressionField('CNT', 'SUM(CNT)'),
new ExpressionField('CNT', 'SUM(CNT)'),
),
"filter" => array(
">=DATE_STAT" => $date,
@@ -80,7 +82,7 @@ class SenderSendCounter
"CNT" => $increment,
);
$update = array(
"CNT" => new \Bitrix\Main\DB\SqlExpression("?# + ?i", "CNT", $increment),
"CNT" => new SqlExpression("?# + ?i", "CNT", $increment),
);
Internal\SenderSendCounterTable::mergeData($insert, $update);

View File

@@ -2,11 +2,12 @@
namespace Bitrix\Main\Numerator\Model;
use Bitrix\Main\Entity\DataManager;
use Bitrix\Main\Entity\DatetimeField;
use Bitrix\Main\Entity\IntegerField;
use Bitrix\Main\Entity\StringField;
use Bitrix\Main\Entity\UpdateResult;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\DatetimeField;
use Bitrix\Main\ORM\Fields\IntegerField;
use Bitrix\Main\ORM\Fields\StringField;
use Bitrix\Main\ORM\Data\UpdateResult;
use Bitrix\Main\ORM\Data\AddResult;
use Bitrix\Main\Error;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\Numerator\Generator\Contract\Sequenceable;
@@ -160,7 +161,7 @@ class NumeratorTable extends DataManager
/**
* @param $numeratorId
* @param $numeratorFields
* @return \Bitrix\Main\Entity\AddResult|UpdateResult
* @return AddResult|UpdateResult
* @throws SystemException
* @throws \Bitrix\Main\ArgumentException
* @throws \Bitrix\Main\ObjectException

View File

@@ -1,10 +1,11 @@
<?php
namespace Bitrix\Main\Numerator\Model;
use Bitrix\Main\DB\SqlQueryException;
use Bitrix\Main\Entity\DataManager;
use Bitrix\Main\Entity\IntegerField;
use Bitrix\Main\Entity\StringField;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\IntegerField;
use Bitrix\Main\ORM\Fields\StringField;
use Bitrix\Main\Application;
use Bitrix\Main\Result;
@@ -172,4 +173,4 @@ class NumeratorSequenceTable extends DataManager
throw $exc;
}
}
}
}

View File

@@ -1,9 +1,9 @@
<?
<?php
namespace Bitrix\Main\Numerator;
use Bitrix\Main\Entity\ExpressionField;
use Bitrix\Main\Entity\Query;
use Bitrix\Main\ORM\Fields\ExpressionField;
use Bitrix\Main\ORM\Query\Query;
use Bitrix\Main\Error;
use Bitrix\Main\Event;
use Bitrix\Main\Localization\Loc;
@@ -268,7 +268,7 @@ class Numerator
/**
* @param $numId
* @param $config - same configuration structure as using via setConfig method
* @return \Bitrix\Main\Entity\AddResult|\Bitrix\Main\Entity\UpdateResult|Result
* @return \Bitrix\Main\ORM\Data\AddResult|\Bitrix\Main\ORM\Data\UpdateResult|Result
* @throws \Bitrix\Main\ArgumentException
* @throws \Bitrix\Main\NotImplementedException
* @throws \Bitrix\Main\ObjectException
@@ -315,7 +315,7 @@ class Numerator
}
/**
* @return \Bitrix\Main\Entity\AddResult|\Bitrix\Main\Entity\UpdateResult
* @return \Bitrix\Main\ORM\Data\AddResult|\Bitrix\Main\ORM\Data\UpdateResult
* @throws \Bitrix\Main\ArgumentException
* @throws \Bitrix\Main\ObjectException
* @throws \Bitrix\Main\ObjectPropertyException
@@ -400,7 +400,7 @@ class Numerator
/**
* @param $id
* @return $this|\Bitrix\Main\Entity\DeleteResult|Result
* @return $this|\Bitrix\Main\ORM\Data\DeleteResult|Result
* @throws \Exception
*/
public static function delete($id)

View File

@@ -1,7 +1,8 @@
<?php
namespace Bitrix\Main\Numerator\Service;
use Bitrix\Main\Entity\ReferenceField;
use Bitrix\Main\ORM\Fields\Relations\Reference;
use Bitrix\Main\ORM\Data;
use Bitrix\Main\Error;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\Numerator\Generator\SequentNumberGenerator;
@@ -29,7 +30,7 @@ class NumeratorRequestManager
}
/**
* @return \Bitrix\Main\Entity\AddResult|\Bitrix\Main\Entity\UpdateResult|Result
* @return Data\AddResult|Data\UpdateResult|Result
* @throws \Bitrix\Main\ArgumentException
* @throws \Bitrix\Main\NotImplementedException
* @throws \Bitrix\Main\ObjectException
@@ -79,7 +80,7 @@ class NumeratorRequestManager
{
$sequence = NumeratorTable::query()
->registerRuntimeField('',
new ReferenceField(
new Reference(
'ref',
NumeratorSequenceTable::class,
['=this.ID' => 'ref.NUMERATOR_ID']

View File

@@ -183,7 +183,7 @@ abstract class Collection implements \ArrayAccess, \Iterator, \Countable
/**
* @param $primary
*
* @return EntityObject
* @return EntityObject|null
* @throws ArgumentException
*/
final public function getByPrimary($primary)

View File

@@ -1905,7 +1905,7 @@ abstract class EntityObject implements ArrayAccess
{
$fieldName = StringHelper::strtoupper($fieldName);
if (!isset($this->_actualValues[$fieldName]))
if (!array_key_exists($fieldName, $this->_actualValues))
{
// regular field
$list[] = $fieldName;
@@ -1937,7 +1937,8 @@ abstract class EntityObject implements ArrayAccess
{
$fieldMask = $field->getTypeMask();
if (!isset($this->_actualValues[StringHelper::strtoupper($field->getName())])
if (
!array_key_exists(StringHelper::strtoupper($field->getName()), $this->_actualValues)
&& ($mask & $fieldMask)
)
{

View File

@@ -181,7 +181,7 @@ class Expression
*
* @return string
*/
protected static function getTmpName($postfix)
public static function getTmpName($postfix)
{
return 'A'.strtoupper(Random::getString(6).'_'.$postfix);
}

View File

@@ -127,7 +127,14 @@ class Condition
*/
public function getDefinition()
{
return $this->getColumn();
$definition = $this->getColumn();
if ($definition instanceof ColumnExpression)
{
$definition = $definition->getDefinition();
}
return $definition;
}
/**

View File

@@ -105,11 +105,22 @@ class QueryHelper
// original sort
if (!empty($rows))
{
/**
* @var \Bitrix\Main\ORM\Objectify\Collection $sortedCollection
*/
$sortedCollection = $query->getEntity()->createCollection();
foreach ($rows as $row)
{
$sortedCollection->add($collection->getByPrimary($row));
$collectionItem = $collection->getByPrimary($row);
if ($collectionItem)
{
$sortedCollection->add($collectionItem);
}
else
{
trigger_error('Phantom reading', E_USER_WARNING);
}
}
$collection = $sortedCollection;

View File

@@ -2,8 +2,9 @@
namespace Bitrix\Main\Service\GeoIp;
use Bitrix\Main\Entity;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\Validators\LengthValidator;
/**
* Class HandlerTable
@@ -33,7 +34,7 @@ use Bitrix\Main\Localization\Loc;
* @method static \Bitrix\Main\Service\GeoIp\EO_Handler_Collection wakeUpCollection($rows)
*/
class HandlerTable extends Entity\DataManager
class HandlerTable extends DataManager
{
/**
* Returns DB table name for entity.
@@ -90,7 +91,7 @@ class HandlerTable extends Entity\DataManager
public static function validateClassName()
{
return array(
new Entity\Validator\Length(null, 255),
new LengthValidator(null, 255),
);
}

View File

@@ -1,12 +1,13 @@
<?php
namespace Bitrix\Main\Session\Handlers\Table;
namespace Bitrix\Main\Session\Handlers\Table;
use Bitrix\Main\Application;
use Bitrix\Main\DB\MysqlCommonConnection;
use Bitrix\Main\DB\PgsqlConnection;
use Bitrix\Main\Entity;
use Bitrix\Main\Type;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields;
/**
* Class UserSessionTable
@@ -24,7 +25,7 @@ use Bitrix\Main\Type;
* @method static \Bitrix\Main\Session\Handlers\Table\EO_UserSession wakeUpObject($row)
* @method static \Bitrix\Main\Session\Handlers\Table\EO_UserSession_Collection wakeUpCollection($rows)
*/
class UserSessionTable extends Entity\DataManager
class UserSessionTable extends DataManager
{
/** @var string Connection name used for SQL queries */
public const CONNECTION_NAME = 'user_session';
@@ -41,7 +42,7 @@ class UserSessionTable extends Entity\DataManager
/**
* Returns connection name for entity
* Have side affect, keep it in mind!
* Has side effect, keep it in mind!
* Clone default database connection if connection {@link SessionTable::CONNECTION_NAME} doesn't exists
*
* @return string
@@ -69,14 +70,14 @@ class UserSessionTable extends Entity\DataManager
public static function getMap()
{
return [
new Entity\StringField('SESSION_ID', [
new Fields\StringField('SESSION_ID', [
'primary' => true,
'format' => '#^[0-9a-z\-,]{6,250}$#iD'
]),
new Entity\DatetimeField('TIMESTAMP_X', [
new Fields\DatetimeField('TIMESTAMP_X', [
'default_value' => new Type\DateTime
]),
new Entity\TextField('SESSION_DATA', [
new Fields\TextField('SESSION_DATA', [
'default_value' => '',
'save_data_modification' => function() {
return [

View File

@@ -50,13 +50,17 @@ class StringHelper
* Changes registry from snake_case or SNAKE_CASE to CamelCase
*
* @param $str
*
* @param bool $lowCaseFirst
* @return mixed
*/
public static function snake2camel($str)
public static function snake2camel($str, bool $lowCaseFirst = false)
{
$str = str_replace('_', ' ', mb_strtolower($str));
return str_replace(' ', '', ucwords($str));
$str = str_replace(' ', '', ucwords($str));
return $lowCaseFirst
? lcfirst($str)
: $str;
}
/**

View File

@@ -797,12 +797,6 @@ In addition to the Google Terms of Service (http://www.google.com/accounts/TOS),
->setCopyright("Copyright 2012 The Apache Software Foundation")
->setLicence(static::LICENCE_APACHE2),
// faceid/install/components/bitrix/faceid.tracker/templates/.default/smoother.js
// faceid/install/js/faceid/WebPhotoMaker/smoother.js
(new static("Smoother"))
->setCopyright("Copyright 2014 Martin Tschirsich")
->setLicence(static::LICENCE_MIT),
// ui/install/js/ui/fonts/comforter-brush
(new static("Font Comforter Brush"))
->setCopyright("Copyright 2015 The Comforter Brush Project Authors")
@@ -862,6 +856,32 @@ In addition to the Google Terms of Service (http://www.google.com/accounts/TOS),
->setProductUrl('https://github.com/simshaun/recurr')
->setLicence(static::LICENCE_MIT)
->setLicenceUrl('https://github.com/simshaun/recurr/blob/master/LICENSE'),
// mobile/install/mobileapp/mobile/extensions/bitrix/statemanager/redux
(new static('Redux Toolkit'))
->setCopyright('Copyright (c) 2018 Mark Erikson')
->setProductUrl('https://redux-toolkit.js.org')
->setLicence(Copyright::LICENCE_MIT),
(new static('Redux-State-Sync 3'))
->setCopyright('Copyright (c) 2018 MU AOHUA')
->setProductUrl('https://github.com/AOHUA/redux-state-sync')
->setLicence(Copyright::LICENCE_MIT),
(new static('Logger for Redux'))
->setCopyright('Copyright (c) 2016 Eugene Rodionov')
->setProductUrl('https://github.com/LogRocket/redux-logger')
->setLicence(Copyright::LICENCE_MIT),
(new static('redux-batched-subscribe'))
->setCopyright('Copyright (c) 2016 Terry Appleby')
->setProductUrl('https://github.com/tappleby/redux-batched-subscribe')
->setLicence(Copyright::LICENCE_MIT),
(new static('redux-batched-actions'))
->setCopyright('Copyright (c) 2016 Tim Shelburne')
->setProductUrl('https://github.com/tshelburne/redux-batched-actions')
->setLicence(Copyright::LICENCE_MIT),
];
}
}

View File

@@ -54,11 +54,14 @@ class PageNavigation
if(($value = $request->getQuery($this->id)) !== null)
{
//parameters are in the QUERY_STRING
$params = explode("-", $value);
for($i = 0, $n = count($params); $i < $n; $i += 2)
if (is_string($value))
{
$navParams[$params[$i]] = $params[$i+1];
//parameters are in the QUERY_STRING
$params = explode("-", $value);
for($i = 0, $n = count($params); $i < $n; $i += 2)
{
$navParams[$params[$i]] = $params[$i+1];
}
}
}
else

View File

@@ -8,6 +8,7 @@ use Bitrix\Main\Localization\Loc;
use Bitrix\Main\ModuleManager;
use Bitrix\Main\FinderDestTable;
use Bitrix\Main\Loader;
use Bitrix\Main\ORM\Fields\ExpressionField;
class Entities
{
@@ -281,6 +282,8 @@ class Entities
public static function getLastSort($params = array())
{
global $USER;
$result = array(
'DATA' => array(),
'DATA_ADDITIONAL' => array()
@@ -392,7 +395,7 @@ class Entities
$helper = $conn->getSqlHelper();
$runtime = array(
new \Bitrix\Main\Entity\ExpressionField('CONTEXT_SORT', "CASE WHEN CONTEXT = '".$helper->forSql(mb_strtoupper($params["DEST_CONTEXT"]))."' THEN 1 ELSE 0 END")
new ExpressionField('CONTEXT_SORT', "CASE WHEN CONTEXT = '".$helper->forSql(mb_strtoupper($params["DEST_CONTEXT"]))."' THEN 1 ELSE 0 END")
);
$order = array(
@@ -767,7 +770,7 @@ class Entities
{
$selectList[] = 'UF_USER_CRM_ENTITY';
}
$selectList[] = new \Bitrix\Main\Entity\ExpressionField('MAX_LAST_USE_DATE', 'MAX(%s)', array('\Bitrix\Main\FinderDest:CODE_USER_CURRENT.LAST_USE_DATE'));
$selectList[] = new ExpressionField('MAX_LAST_USE_DATE', 'MAX(%s)', array('\Bitrix\Main\FinderDest:CODE_USER_CURRENT.LAST_USE_DATE'));
$res = \Bitrix\Main\UserTable::getList(array(
'order' => array(

View File

@@ -1,9 +1,9 @@
<?
<?php
namespace Bitrix\Main\UI\Viewer;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\Entity;
use Bitrix\Main\ORM\Fields;
use Bitrix\Main\FileTable;
use Bitrix\Main\ORM\Event;
use Bitrix\Main\Type\Date;
@@ -42,41 +42,43 @@ final class FilePreviewTable extends DataManager
/**
* Returns entity map definition.
* To get initialized fields @see \Bitrix\Main\Entity\Base::getFields() and \Bitrix\Main\Entity\Base::getField()
* To get initialized fields
* @return array
* @throws \Bitrix\Main\SystemException
* @see \Bitrix\Main\ORM\Entity::getFields()
* @see \Bitrix\Main\ORM\Entity::getField()
*/
public static function getMap()
{
return [
new Entity\IntegerField('ID', [
new Fields\IntegerField('ID', [
'primary' => true,
'autocomplete' => true,
]),
new Entity\IntegerField('FILE_ID', [
new Fields\IntegerField('FILE_ID', [
'required' => true,
]),
new Entity\IntegerField('PREVIEW_ID'),
new Entity\IntegerField('PREVIEW_IMAGE_ID'),
new Entity\DatetimeField('CREATED_AT', [
new Fields\IntegerField('PREVIEW_ID'),
new Fields\IntegerField('PREVIEW_IMAGE_ID'),
new Fields\DatetimeField('CREATED_AT', [
'default_value' => function () {
return new DateTime();
},
]),
new Entity\DatetimeField('TOUCHED_AT', [
new Fields\DatetimeField('TOUCHED_AT', [
'default_value' => function () {
return new DateTime();
},
]),
new Entity\ReferenceField('FILE',FileTable::class,
new Fields\Relations\Reference('FILE',FileTable::class,
['=this.FILE_ID' => 'ref.ID'],
['join_type' => 'INNER']
),
new Entity\ReferenceField('PREVIEW',FileTable::class,
new Fields\Relations\Reference('PREVIEW',FileTable::class,
['=this.PREVIEW_ID' => 'ref.ID'],
['join_type' => 'LEFT']
),
new Entity\ReferenceField('PREVIEW_IMAGE',FileTable::class,
new Fields\Relations\Reference('PREVIEW_IMAGE',FileTable::class,
['=this.PREVIEW_IMAGE_ID' => 'ref.ID'],
['join_type' => 'LEFT']
),

View File

@@ -2,7 +2,7 @@
namespace Bitrix\Main\Update;
use Bitrix\Main\Entity;
use Bitrix\Main\ORM\Data\DataManager;
/**
* Class VersionHistoryTable
@@ -21,7 +21,7 @@ use Bitrix\Main\Entity;
* @method static \Bitrix\Main\Update\EO_VersionHistory wakeUpObject($row)
* @method static \Bitrix\Main\Update\EO_VersionHistory_Collection wakeUpCollection($rows)
*/
class VersionHistoryTable extends Entity\DataManager
class VersionHistoryTable extends DataManager
{
/**
* Returns DB table name for entity

View File

@@ -3,10 +3,10 @@
namespace Bitrix\Main\UrlPreview;
use Bitrix\Main\Application;
use Bitrix\Main\Entity;
use Bitrix\Main;
use Bitrix\Main\Entity\AddResult;
use Bitrix\Main\Entity\ScalarField;
use Bitrix\Main\ORM\Data\AddResult;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields;
/**
* Class RouteTable
@@ -24,9 +24,8 @@ use Bitrix\Main\Entity\ScalarField;
* @method static \Bitrix\Main\UrlPreview\EO_Route wakeUpObject($row)
* @method static \Bitrix\Main\UrlPreview\EO_Route_Collection wakeUpCollection($rows)
*/
class RouteTable extends Entity\DataManager
class RouteTable extends DataManager
{
/**
* Returns DB table name for entity
*
@@ -45,21 +44,21 @@ class RouteTable extends Entity\DataManager
public static function getMap()
{
return array(
'ID' => new Entity\IntegerField('ID', array(
'ID' => new Fields\IntegerField('ID', array(
'primary' => true,
'autocomplete' => true,
)),
'ROUTE' => new Entity\StringField('ROUTE', array(
'ROUTE' => new Fields\StringField('ROUTE', array(
'required' => true,
'unique' => true
)),
'MODULE' => new Entity\StringField('MODULE', array(
'MODULE' => new Fields\StringField('MODULE', array(
'required' => true,
)),
'CLASS' => new Entity\StringField('CLASS', array(
'CLASS' => new Fields\StringField('CLASS', array(
'required' => true,
)),
'PARAMETERS' => new Entity\TextField('PARAMETERS', array(
'PARAMETERS' => new Fields\TextField('PARAMETERS', array(
'serialized' => true,
)),
);
@@ -99,7 +98,7 @@ class RouteTable extends Entity\DataManager
// set fields with default values
foreach (static::getEntity()->getFields() as $field)
{
if ($field instanceof ScalarField && !array_key_exists($field->getName(), $data))
if ($field instanceof Fields\ScalarField && !array_key_exists($field->getName(), $data))
{
$defaultValue = $field->getDefaultValue();
@@ -157,4 +156,4 @@ class RouteTable extends Entity\DataManager
return $result;
}
}
}

View File

@@ -3,9 +3,10 @@
namespace Bitrix\Main\UrlPreview;
use Bitrix\Main;
use Bitrix\Main\Entity;
use Bitrix\Main\Text\Emoji;
use Bitrix\Main\ORM\Event;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields;
/**
* Class UrlMetadataTable
@@ -23,7 +24,7 @@ use Bitrix\Main\ORM\Event;
* @method static \Bitrix\Main\UrlPreview\EO_UrlMetadata wakeUpObject($row)
* @method static \Bitrix\Main\UrlPreview\EO_UrlMetadata_Collection wakeUpCollection($rows)
*/
class UrlMetadataTable extends Entity\DataManager
class UrlMetadataTable extends DataManager
{
const TYPE_STATIC = 'S';
const TYPE_DYNAMIC = 'D';
@@ -48,37 +49,37 @@ class UrlMetadataTable extends Entity\DataManager
public static function getMap()
{
return array(
'ID' => new Entity\IntegerField('ID', array(
'ID' => new Fields\IntegerField('ID', array(
'primary' => true,
'autocomplete' => true,
)),
'URL' => new Entity\StringField('URL', array(
'URL' => new Fields\StringField('URL', array(
'required' => true,
)),
'TYPE' => new Entity\StringField('TYPE', array(
'TYPE' => new Fields\StringField('TYPE', array(
'required' => true,
)),
'TITLE' => new Entity\StringField('TITLE', [
'TITLE' => new Fields\StringField('TITLE', [
'save_data_modification' => [Emoji::class, 'getSaveModificator'],
'fetch_data_modification' => [Emoji::class, 'getFetchModificator'],
]),
'DESCRIPTION' => new Entity\TextField('DESCRIPTION', [
'DESCRIPTION' => new Fields\TextField('DESCRIPTION', [
'save_data_modification' => [Emoji::class, 'getSaveModificator'],
'fetch_data_modification' => [Emoji::class, 'getFetchModificator'],
]),
'IMAGE_ID' => new Entity\IntegerField('IMAGE_ID'),
'IMAGE' => new Entity\StringField('IMAGE'),
'EMBED' => new Entity\TextField('EMBED', [
'IMAGE_ID' => new Fields\IntegerField('IMAGE_ID'),
'IMAGE' => new Fields\StringField('IMAGE'),
'EMBED' => new Fields\TextField('EMBED', [
'save_data_modification' => [Emoji::class, 'getSaveModificator'],
'fetch_data_modification' => [Emoji::class, 'getFetchModificator'],
]),
'EXTRA' => new Entity\TextField('EXTRA', array(
'EXTRA' => new Fields\TextField('EXTRA', array(
'serialized' => true,
)),
'DATE_INSERT' => new Entity\DatetimeField('DATE_INSERT', array(
'DATE_INSERT' => new Fields\DatetimeField('DATE_INSERT', array(
'default_value' => new Main\Type\DateTime(),
)),
'DATE_EXPIRE' => new Entity\DatetimeField('DATE_EXPIRE')
'DATE_EXPIRE' => new Fields\DatetimeField('DATE_EXPIRE')
);
}

View File

@@ -8,11 +8,13 @@
namespace Bitrix\Main\UserConsent\Internals;
use Bitrix\Main\Application;
use Bitrix\Main\Entity;
use Bitrix\Main\Type\DateTime;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\Security\Random;
use Bitrix\Main\UserConsent\Agreement;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Event;
use Bitrix\Main\ORM\EventResult;
Loc::loadMessages(__FILE__);
@@ -32,7 +34,7 @@ Loc::loadMessages(__FILE__);
* @method static \Bitrix\Main\UserConsent\Internals\EO_Agreement wakeUpObject($row)
* @method static \Bitrix\Main\UserConsent\Internals\EO_Agreement_Collection wakeUpCollection($rows)
*/
class AgreementTable extends Entity\DataManager
class AgreementTable extends DataManager
{
/**
* Get table name.
@@ -120,15 +122,15 @@ class AgreementTable extends Entity\DataManager
/**
* After delete event handler.
*
* @param Entity\Event $event Event object.
* @return Entity\EventResult
* @param Event $event Event object.
* @return EventResult
*/
public static function onAfterDelete(Entity\Event $event)
public static function onAfterDelete(Event $event)
{
$result = new Entity\EventResult;
$result = new EventResult;
$data = $event->getParameters();
$sql = /** @lang MySQL */ "DELETE FROM " . ConsentTable::getTableName() . " WHERE AGREEMENT_ID = " . intval($data['primary']['ID']);
$sql = "DELETE FROM " . ConsentTable::getTableName() . " WHERE AGREEMENT_ID = " . intval($data['primary']['ID']);
Application::getConnection()->query($sql);
return $result;

View File

@@ -7,10 +7,10 @@
*/
namespace Bitrix\Main\UserConsent\Internals;
use Bitrix\Main\Entity;
use Bitrix\Main\ORM\Fields\Relations\OneToMany;
use Bitrix\Main\Type\DateTime;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\ORM\Data\DataManager;
Loc::loadMessages(__FILE__);
@@ -30,7 +30,7 @@ Loc::loadMessages(__FILE__);
* @method static \Bitrix\Main\UserConsent\Internals\EO_Consent wakeUpObject($row)
* @method static \Bitrix\Main\UserConsent\Internals\EO_Consent_Collection wakeUpCollection($rows)
*/
class ConsentTable extends Entity\DataManager
class ConsentTable extends DataManager
{
/**
* Get table name.

View File

@@ -7,8 +7,8 @@
*/
namespace Bitrix\Main\UserConsent\Internals;
use Bitrix\Main\Entity;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\ORM\Data\DataManager;
Loc::loadMessages(__FILE__);
@@ -28,7 +28,7 @@ Loc::loadMessages(__FILE__);
* @method static \Bitrix\Main\UserConsent\Internals\EO_Field wakeUpObject($row)
* @method static \Bitrix\Main\UserConsent\Internals\EO_Field_Collection wakeUpCollection($rows)
*/
class FieldTable extends Entity\DataManager
class FieldTable extends DataManager
{
/**
* Get table name.

View File

@@ -7,10 +7,10 @@
*/
namespace Bitrix\Main\UserConsent\Internals;
use Bitrix\Main\Entity;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\ORM\Fields\Relations\Reference;
use Bitrix\Main\ORM\Query\Join;
use Bitrix\Main\ORM\Data\DataManager;
Loc::loadMessages(__FILE__);
@@ -30,7 +30,7 @@ Loc::loadMessages(__FILE__);
* @method static \Bitrix\Main\UserConsent\Internals\EO_UserConsentItem wakeUpObject($row)
* @method static \Bitrix\Main\UserConsent\Internals\EO_UserConsentItem_Collection wakeUpCollection($rows)
*/
class UserConsentItemTable extends Entity\DataManager
class UserConsentItemTable extends DataManager
{
/**
* Get table name.

View File

@@ -3,7 +3,8 @@
namespace Bitrix\Main\UserField\Access\Permission;
use Bitrix\Main\Access\Permission\AccessPermissionTable;
use Bitrix\Main\Entity;
use Bitrix\Main\ORM\Fields;
use Bitrix\Main\ORM\Query\Query;
use Bitrix\Main\ORM\Query\Join;
use Bitrix\Main\ORM\Fields\Relations\Reference;
use Bitrix\Main\UserAccessTable;
@@ -42,23 +43,23 @@ class UserFieldPermissionTable extends AccessPermissionTable
public static function getMap()
{
return [
new Entity\IntegerField('ID', [
new Fields\IntegerField('ID', [
'autocomplete' => true,
'primary' => true
]),
new Entity\IntegerField('ENTITY_TYPE_ID', [
new Fields\IntegerField('ENTITY_TYPE_ID', [
'required' => true
]),
new Entity\IntegerField('USER_FIELD_ID', [
new Fields\IntegerField('USER_FIELD_ID', [
'required' => true
]),
new Entity\StringField('ACCESS_CODE', [
new Fields\StringField('ACCESS_CODE', [
'required' => true
]),
new Entity\StringField('PERMISSION_ID', [
new Fields\StringField('PERMISSION_ID', [
'required' => true
]),
new Entity\IntegerField('VALUE', [
new Fields\IntegerField('VALUE', [
'required' => true
]),
(new Reference(
@@ -131,7 +132,7 @@ class UserFieldPermissionTable extends AccessPermissionTable
*/
public static function getUserFieldsAccessCodes(int $entityTypeID): array
{
$query = new Entity\Query(self::getEntity());
$query = new Query(self::getEntity());
$query->addSelect('USER_FIELD.FIELD_NAME', 'FIELD_NAME');
$query->addSelect('ACCESS_CODE');
$query->addSelect('USER_ACCESS.USER_ID', 'USER_ID');

View File

@@ -5,7 +5,6 @@ namespace Bitrix\Main\UserField\Internal;
use Bitrix\Main\Application;
use Bitrix\Main\DB\MssqlConnection;
use Bitrix\Main\DB\SqlQueryException;
use Bitrix\Main\Entity\Validator\RegExp;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Entity;
@@ -18,6 +17,7 @@ use Bitrix\Main\ORM\Fields\Field;
use Bitrix\Main\ORM\Fields\IntegerField;
use Bitrix\Main\ORM\Fields\Relations\Reference;
use Bitrix\Main\ORM\Fields\StringField;
use Bitrix\Main\ORM\Fields\Validators\RegExpValidator;
use Bitrix\Main\ORM\Query\Query;
use Bitrix\Main\SystemException;
use Bitrix\Main\Text\StringHelper;
@@ -42,7 +42,7 @@ abstract class TypeDataManager extends DataManager
->configureUnique()
->configureSize(100)
->configureFormat('/^[A-Z][A-Za-z0-9]*$/')
->addValidator(new RegExp(
->addValidator(new RegExpValidator(
'/(?<!Table)$/i'
)),
(new StringField('TABLE_NAME'))
@@ -546,4 +546,4 @@ abstract class TypeDataManager extends DataManager
{
return Type::class;
}
}
}

View File

@@ -0,0 +1,109 @@
<?php
/**
* Bitrix Framework
* @package bitrix
* @subpackage main
* @copyright 2001-2025 Bitrix
*/
namespace Bitrix\Main\Web\Http;
use Bitrix\Main\ArgumentException;
/**
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Range
*/
class Range
{
protected int $start;
protected int $end;
public function __construct(?int $start, ?int $end, int $size)
{
if ($start === null && $end !== null)
{
// Returning requested suffix of the file
$this->start = $size - $end;
$this->end = $size - 1;
}
elseif ($start !== null && $end === null)
{
// Returning all bytes with offset
$this->start = $start;
$this->end = $size - 1;
}
else
{
$this->start = (int)$start;
$this->end = (int)$end;
}
if ($this->start > $this->end || $this->start >= $size)
{
throw new ArgumentException('Requested Range Not Satisfiable');
}
if ($this->end >= $size)
{
$this->end = $size - 1;
}
}
public function getStart(): int
{
return $this->start;
}
public function getEnd(): int
{
return $this->end;
}
/**
* @param string $header
* @param int $size
* @return Range[] | null
*/
public static function createFromString(string $header, int $size): ?array
{
if (str_contains($header, '='))
{
[$unit, $value] = explode("=", $header);
if (strtolower(trim($unit)) === 'bytes')
{
$ranges = [];
foreach (explode(',', $value) as $range)
{
if (str_contains($range, '-'))
{
[$start, $end] = explode('-', $range);
if (!is_numeric($start))
{
$start = null;
}
if (!is_numeric($end))
{
$end = null;
}
try
{
$ranges[] = new Range($start, $end, $size);
}
catch (ArgumentException)
{
// all ranges must be correct
return null;
}
}
}
return $ranges ?: null;
}
}
return null;
}
}

View File

@@ -61,6 +61,7 @@ class HttpClient implements Log\LoggerAwareInterface, ClientInterface, Http\Debu
protected $useCurl = false;
protected $curlLogFile = null;
protected $shouldFetchBody = null;
protected $sendEvents = true;
protected Http\ResponseBuilderInterface $responseBuilder;
protected HttpHeaders $headers;
@@ -93,6 +94,7 @@ class HttpClient implements Log\LoggerAwareInterface, ClientInterface, Http\Debu
* "headers" array of headers for HTTP request.
* "useCurl" bool Enable CURL (default false).
* "curlLogFile" string Full path to CURL log file.
* "sendEvents" bool Send events (default true).
* "responseBuilder" Http\ResponseBuilderInterface Response builder.
* Almost all options can be set separately with setters.
*/
@@ -177,6 +179,10 @@ class HttpClient implements Log\LoggerAwareInterface, ClientInterface, Http\Debu
{
$this->curlLogFile = $options['curlLogFile'];
}
if (isset($options['sendEvents']))
{
$this->sendEvents = (bool)$options['sendEvents'];
}
if (isset($options['responseBuilder']))
{
$this->setResponseBuilder($options['responseBuilder']);
@@ -880,13 +886,16 @@ class HttpClient implements Log\LoggerAwareInterface, ClientInterface, Http\Debu
}
}
// Here's the chance to tune up the client and to rebuild the request.
$event = new Http\RequestEvent($this, $request, 'OnHttpClientBuildRequest');
$event->send();
foreach ($event->getResults() as $eventResult)
if ($this->sendEvents)
{
$request = $eventResult->getRequest();
// Here's the chance to tune up the client and to rebuild the request.
$event = new Http\RequestEvent($this, $request, 'OnHttpClientBuildRequest');
$event->send();
foreach ($event->getResults() as $eventResult)
{
$request = $eventResult->getRequest();
}
}
return new Http\Request(

View File

@@ -63,13 +63,16 @@ class Json
public static function validate(string $data): bool
{
if (function_exists('json_validate'))
{
return json_validate($data);
}
// On PHP 8.3 replace to
// return json_validate($data);
try
{
if ($data === 'null') // consistency with json_validate
{
return true;
}
return json_decode(json: $data, associative: true, flags: JSON_THROW_ON_ERROR) !== null;
}
catch (JsonException)

View File

@@ -73,12 +73,27 @@ class Uri implements \JsonSerializable, UriInterface
}
$authority = $this->getAuthority();
$path = $this->getPath();
if ($authority != '')
{
$uri .= '//' . $authority;
if (!str_starts_with($path, '/'))
{
$uri .= '/';
}
$uri .= $path;
}
else
{
$uri .= $path;
}
$uri .= $this->getPathQuery();
$query = $this->getQuery();
if ($query != '')
{
$uri .= '?' . $query;
}
return $uri;
}
@@ -168,7 +183,7 @@ class Uri implements \JsonSerializable, UriInterface
}
/**
* Returns the path with the query.
* Returns the path with the query. Empty path is treated as a root (/)
* @return string
*/
public function getPathQuery()

View File

@@ -0,0 +1,123 @@
<?php
declare(strict_types = 1);
namespace Bitrix\Main\Web\UserAgent;
enum Platform: string
{
case Android = 'Android';
case Ios = 'iOS';
case Windows = 'Windows';
case Macos = 'macOS';
case LinuxRpm = 'Linux RPM';
case LinuxDeb = 'Linux DEB';
case Unknown = 'Unknown';
public function isMobile(): bool
{
return in_array($this, [self::Android, self::Ios], true);
}
public function isDesktop(): bool
{
return in_array($this, [self::Windows, self::Macos, self::LinuxRpm, self::LinuxDeb], true);
}
public function isLinux(): bool
{
return in_array($this, [self::LinuxRpm, self::LinuxDeb], true);
}
public static function fromUserAgent(string $userAgent): self
{
$userAgent = strtolower($userAgent);
if (str_contains($userAgent, 'bitrixmobile') || str_contains($userAgent, 'bitrix24/'))
{
if (
str_contains($userAgent, 'iphone')
|| str_contains($userAgent, 'ipad')
|| str_contains($userAgent, 'darwin')
)
{
return self::Ios;
}
return self::Android;
}
if (self::isWindowsByUserAgent($userAgent))
{
return self::Windows;
}
if (self::isMacosByUserAgent($userAgent))
{
return self::Macos;
}
if (self::isLinuxByUserAgent($userAgent))
{
return self::getLinuxPlatform($userAgent);
}
if (self::isIosByUserAgent($userAgent))
{
return self::Ios;
}
if (self::isAndroidByUserAgent($userAgent) || str_contains($userAgent, 'carddavbitrix24'))
{
return self::Android;
}
return self::Unknown;
}
private static function isWindowsByUserAgent(string $userAgent): bool
{
return str_contains($userAgent, 'windows')
|| str_contains($userAgent, 'win32')
|| str_contains($userAgent, 'win64');
}
private static function isLinuxByUserAgent(string $userAgent): bool
{
return str_contains($userAgent, 'linux') && !self::isAndroidByUserAgent($userAgent);
}
private static function isIosByUserAgent(string $userAgent): bool
{
return str_contains($userAgent, 'iphone')
|| str_contains($userAgent, 'ipad')
|| str_contains($userAgent, 'ipod')
|| (str_contains($userAgent, 'ios') && !str_contains($userAgent, 'windows'));
}
private static function isMacosByUserAgent(string $userAgent): bool
{
return (str_contains($userAgent, 'mac os') && !str_contains($userAgent, 'like mac os x'))
|| str_contains($userAgent, 'macintosh');
}
private static function isAndroidByUserAgent(string $userAgent): bool
{
return str_contains($userAgent, 'android');
}
private static function getLinuxPlatform(string $userAgent): self
{
if (
str_contains($userAgent, 'rpm')
|| str_contains($userAgent, 'fedora')
|| str_contains($userAgent, 'centos')
|| str_contains($userAgent, 'rhel')
)
{
return self::LinuxRpm;
}
return self::LinuxDeb;
}
}