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

@@ -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');
}
}