Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
/REVISION
.AppleDouble
/vendor/*
!/vendor/Smarty/
/auth.json
composer.lock
*.iml
Expand Down
44 changes: 44 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,49 @@
# Change log

## [4.0.0] 2026-07-15

**BREAKING**: Smarty полностью удалён из ядра. Проектам, которым нужен Smarty,
следует оставаться на ветке 3.x.

- Удалены Smarty 2 (`vendor/Smarty`), `PXUserHTMLLayout`, `lib/smarty.plugins/`
и Smarty-версии шаблонов пейджера (`templates/misc/pager/*.tmpl`)
- Клиентский шаблонизатор по умолчанию — `TwigLayout` (Twig 3); `twig/twig`
теперь обязательная зависимость
- Шаблонизатор по-прежнему подменяем через DI: публичный сервис
`PP\Lib\Html\Layout\LayoutInterface` в services.yml проекта (обязан
реализовывать `UserLayoutInterface`)
- Плагины `createpath`, `img`, `html_import`, `jquery`, `assets_apply`,
`date_to_time` портированы в `PP\Lib\Html\Twig\CoreFunctionsExtension`
и регистрируются `TwigLayout` из коробки
- **BREAKING**: из `LayoutInterface` удалён `getSmarty()`; добавлены
`display()` и `setDebug()` — кастомные реализации интерфейса нужно обновить
- Пагинация (`pager`/`autopager`, `templates/misc/pager/*.twig`) и
Smarty-совместимые фильтры (`PP\Lib\Html\Twig\SmartyCompatExtension`) —
вывод байт-в-байт совместим с рендером Smarty-версии
- Консольная команда `templates:convert` — конвертация исходников Smarty 2 → Twig
с отчётом о местах, требующих ручного вмешательства, см. docs/templates.md;
`===`/`!==` → twig-тест `is same as()`, `{section step=1}` — штатный,
корректный разбор пути после динамического ключа (`$a->b.$k->c` → `a.b[k].c`),
бэкслеши в строковых литералах перекодируются под лексер Twig,
`{capture name=X assign=Y}` заполняет обе переменные,
`{section}` с `step`/`max`/`show` остаётся `{# UNCONVERTED #}`,
smarty-флаг `nocache` отбрасывается с предупреждением
- Исправлена инициализация фабрик в `AbstractEngine`: поддержка интерфейсов в качестве
фабрики (через init-хелпер и DI-контейнер); контейнер компилируется до
получения layout-сервиса из DI
- `PXStructLeaf`/`PXTreeObjects`: `__call` кидает `BadMethodCallException` вместо
`FatalError` (Twig перехватывает его при разрешении атрибутов), добавлен
`PXStructLeaf::__isset`, `PXTreeObjects::__isset` возвращает true для `current`/`root` —
доступ к атрибутам из Twig идёт через магию `__get`, как в Smarty, а не через
одноимённые методы `Iterator`
- `PXObjects::__isset`: доступ `objects.current`/`objects.first` из Twig идёт через
защищённый `__get`, а не через незащищённый `getCurrent()` (давал Warning
«Undefined array key -1» и null на страницах без текущего объекта)
- `TwigLayout`: фолбэк модификаторов на PHP-функции, принимающие аргумент по ссылке
(`reset`, `end`, `current`, `key`, ...), больше не приводит к ошибке рендера;
добавлен `getVarByRef()`; выходные фильтры хранятся списком callable —
объектные и closure-колбэки в `addFilter()` больше не ломаются

## [3.1.11] 2026-02-24
- Улучшение безопасности работы с сессией в админ панели

Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"symfony/dependency-injection": "^6.0",
"symfony/finder": "^6.0",
"symfony/cache": "^6.0",
"twig/twig": "^3.0",
"ext-json": "*",
"ext-pcntl": "*",
"ext-posix": "*",
Expand Down
2 changes: 1 addition & 1 deletion docs/properties.module.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
## Особенности работы

* Для использования `$app->getProperty` необходимо чтобы класс был инициализирован одним из `PXEngine`
* Для `Smarty` создана функция и модификатор `property`
* Для шаблонов (Twig) созданы функция и модификатор `property`
* Параметры в названии которых присутствует префикс `SYS_` и описанные в `modules.yml` трактуются как системные
* Для редактирования системных параметров используется ACL правило модуля `properties`
* StorageType всегда строка
Expand Down
131 changes: 131 additions & 0 deletions docs/templates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Шаблонизатор: Twig и миграция со Smarty 2

Начиная с версии 4.0.0 клиентский движок шаблонов — **Twig 3** (`TwigLayout`).
Smarty полностью удалён из ядра: ни рантайма, ни плагинов, ни фолбэка.
Проектам, которым нужен Smarty, следует оставаться на ветке 3.x.

## Выбор движка через DI

По умолчанию, без какой-либо конфигурации, используется
`PP\Lib\Html\Layout\TwigLayout`. Проект может подменить движок, определив в
`app/config/services.yml` **публичный** сервис с id
`PP\Lib\Html\Layout\LayoutInterface`:

```yaml
services:
PP\Lib\Html\Layout\LayoutInterface:
class: App\Layout\MyCustomLayout
public: true
```

`PXEngineIndex::initLayout()` заберёт сервис из контейнера; сервис обязан
реализовывать `PP\Lib\Html\Layout\UserLayoutInterface`.

Не забудьте сбросить кэш контейнера (`CACHE_PATH/container.php`) после
изменения `services.yml`.

## TwigLayout

- Шаблоны ищутся в `local/templates/`, затем `libpp/templates/`,
расширение `.twig`. Легаси-имена `.tmpl`, приходящие из PHP-кода
(`$layout->html('misc/pager/pages.tmpl')`), прозрачно переписываются
в `.twig`.
- Автоэкранирование выключено, неопределённые переменные не являются
ошибкой — так же, как было в Smarty 2 (важно для конвертированных
шаблонов).
- Неизвестные фильтры и функции Twig резолвятся в глобальные PHP-функции
(`{{ x|quot }}`, `{{ smarty_file_exists('a.twig') }}`) — аналог
фолбэка модификаторов Smarty 2 на PHP-функции.
- Smarty-совместимые фильтры (`smarty_escape`, `smarty_default`,
`smarty_replace`, `cat`, `strip`, `date_format`, `regex_replace`,
`truncate`, `isset`, `empty`, ...) живут в
`PP\Lib\Html\Twig\SmartyCompatExtension`.
- Функции ядра (`PP\Lib\Html\Twig\CoreFunctionsExtension` и `TwigLayout`)
зарегистрированы из коробки: `property`, `lang`, `pager`, `autopager`,
`createpath`, `img`, `html_import`, `jquery`, `layout_var`, `pager_href`;
модификаторы `property`, `lang`, `date_to_time`; блок `assets_apply`
(`{% apply assets_apply %}...{% endapply %}`).
- Проектные функции/модификаторы регистрируются так же, как раньше:
`$layout->addTemplateFunction('name', $callback)` /
`addTemplateModifier()` — колбэки в Smarty-стиле
`($params, &$smarty)` работают без изменений.
- Пагинация: `{{ pager({objects: ..., format: ...}) }}` и
`{{ autopager({format: ...}) }}` рендерят `misc/pager/pages.twig`
(байт-в-байт совместимо с рендером Smarty-версии из 3.x).

## Конвертация шаблонов: `pp templates:convert`

Команда конвертирует **исходники** Smarty 2 (`.tmpl`) в Twig (`.twig`) —
Smarty-рантайм для этого не нужен. Используйте её при переводе проекта
с pp-core 3.x на 4.x.

```bash
pp templates:convert # BASEPATH/local/templates рекурсивно
pp templates:convert local/templates/lt # только поддерево
pp templates:convert --dry-run # отчёт без записи файлов
pp templates:convert --force # перезаписать существующие .twig
```

Каждый `foo.tmpl` конвертируется в `foo.twig` рядом с исходником.
Статусы в отчёте:

- `OK` — конвертирован полностью;
- `CHECK` — конвертирован, но есть предупреждения (например,
`{assign}` внутри цикла: в Twig `{% set %}` локален для цикла,
а в Smarty присваивание «протекает» наружу — если переменная
читается после цикла, нужен ручной рефакторинг; или модификатор,
не известный конвертеру, — убедитесь, что проект регистрирует его
через `addTemplateModifier()`);
- `MANUAL` — есть конструкции без автоматического аналога
(`{php}`, `{break}`/`{continue}`, `{section step=...}`).
Такие места остаются в выводе в виде `{# UNCONVERTED: ... #}`
с указанием файла и строки в отчёте;
- `SKIP` — `.twig` уже существует (перезапись — через `--force`).

Конвертер сохраняет байтовую совместимость вывода, включая тонкости
Smarty 2 с переносами строк после тегов и семантику `{strip}`.

Предупреждения о функциях, «не определённых в CLI» — это функции,
доступные только в веб-бутстрапе проекта: убедитесь, что они существуют
в рантайме, и игнорируйте предупреждение.

## Проверка результата

Сравнивайте рендер ключевых страниц до миграции (pp-core 3.x, Smarty)
и после (4.x, Twig): снимите HTML (`curl`) на обеих версиях и сравните.
Расхождения допустимы только в случайных значениях (nonce, csrf-токены)
и незначащих пробелах.

## Известные ограничения автоконвертации

| Конструкция | Что делать |
|---|---|
| `{php}...{/php}` | переносить логику в модуль/функцию вручную |
| `{break}`, `{continue}` | в Twig нет; переписать через `filter`/условие |
| `{section step=... / max=... / show=...}` | остаётся `{# UNCONVERTED #}`; переписать на `{% for %}` вручную |
| `{assign}` в цикле, читаемый после цикла | вынести из цикла или собрать через `layout_var` |
| `{cycle}`, `{counter}`, `{eval}`, `{fetch}`, `{html_*}` | ручной порт (используются крайне редко) |

`===`/`!==` конвертируются автоматически в twig-тест `is same as(...)`
(строгое сравнение сохраняется). `{capture name=X assign=Y}` заполняет
обе переменные (`$Y` и `$smarty.capture.X`), как в Smarty.

## Отличие: области видимости `{include}` с параметрами

В Smarty 2 из pp-core 3.x переменные, переданные в
`{include file=... var=...}`, из-за особенности реализации (`_tpl_vars` —
объект) оставались видимыми и **после** include — «протекали» в шаблоны,
рендерящиеся следом. В Twig `{% include ... with {...} %}` таких утечек нет:
переданные переменные видны только внутри включаемого шаблона.

Если проект полагается на такую утечку (сосед-шаблон читает переменную,
которую кто-то раньше передал в include), после миграции переменная станет
пустой — проверяйте такие места сравнением отрендеренных страниц.

## Ручные правки после переконвертации

`templates:convert --force` перезаписывает все `.twig`, включая портированные
вручную ({break}/{continue} и т.п.). Держите ручные правки идемпотентным
патч-скриптом рядом с шаблонами и накатывайте его после каждой
переконвертации (пример: `local/templates/twig-manual-patches.php` в проекте
sbertelecom — str_replace-патчи со статусами OK/SKIP/FAIL).
38 changes: 35 additions & 3 deletions lib/Engine/index.class.inc
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
use PP\Lib\Http\Response;
use PP\DependencyInjection\ContainerAwareInterface;
use PP\Lib\Engine\AbstractEngine;
use PP\Lib\Html\Layout\LayoutInterface;
use PP\Lib\Html\Layout\TwigLayout;
use PP\Lib\Html\Layout\UserLayoutInterface;

class PXEngineIndex extends AbstractEngine
{
public $layout = ['factory' => 'PXUserHTMLLayout'];
public $layout = ['factory' => LayoutInterface::class, 'helper' => true];

/**
* Инициализация основных объектов приложения
Expand All @@ -20,14 +23,43 @@ class PXEngineIndex extends AbstractEngine
$this->content->tree = new PXTreeObjects();
$this->content->objects = new PXObjects();
$this->content->subObjects = new PXSubObjects();

$this->layout->setApp($this->app);
}

protected function initModules()
{
}

/**
* Template engine is swappable: project may define a public service
* with id `PP\Lib\Html\Layout\LayoutInterface` in app/config/services.yml.
* Without the service the default TwigLayout is used.
*
* @param string $layoutClass
*/
protected function initLayout($layoutClass)
{
// the layout is resolved during initApplication(), before the engine
// constructor compiles the container — compile it now so the service
// is instantiated from a fully processed definition
$this->compileContainer();

if ($this->container->has($layoutClass)) {
$layout = $this->container->get($layoutClass);

if (!$layout instanceof UserLayoutInterface) {
FatalError(sprintf(
"Layout service '%s' must implement %s",
$layoutClass,
UserLayoutInterface::class
));
}
} else {
$layout = new TwigLayout();
}

$this->layout = $layout->setApp($this->app);
}

public function fillLayout()
{
$this->layout->setApp(PXRegistry::getApp());
Expand Down
4 changes: 2 additions & 2 deletions lib/HTML/lang.class.inc
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class PXUserHTMLLang
}

/**
* smarty_modifier_lang
* Template modifier: {{ path|lang }}
*/
public function lang_modifier($path)
{
Expand All @@ -32,7 +32,7 @@ class PXUserHTMLLang
}

/**
* smarty_function_lang
* Template function: {{ lang({get: ...}) }}
* @return string
*/
public function lang($param, &$smarty)
Expand Down
Loading