| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Adminx\Rubrics\FieldEditors;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
class RelationFieldEditor extends AbstractFieldEditor
{
public function parseValue($type, $value)
{
$type = (string) $type;
$value = (string) $value;
if ($type === 'tags') {
$items = $this->structuredScalars($value);
return array('raw' => $value, 'tags' => $items === null ? $this->pipeItems($value) : $this->cleanItems($items));
}
if (in_array($type, array('doc_from_rub_check', 'doc_from_rub_search'), true)) {
$items = $this->structuredScalars($value);
return array('raw' => $value, 'document_ids' => $items === null ? $this->pipeItems($value) : $this->documentIds($items));
}
if (in_array($type, array('analoque', 'teasers'), true)) {
return array('raw' => $value, 'document_ids' => $this->documentIds($value));
}
if ($type === 'doc_from_rub') {
return array('raw' => $value, 'document_id' => trim($value));
}
if ($type === 'catalog') {
$data = @unserialize($value, array('allowed_classes' => false));
if (!is_array($data)) {
$data = array();
}
return array(
'raw' => $value,
'catalog_ids' => $this->csvItems(isset($data['catalogs']) ? $data['catalogs'] : ''),
'document_ids' => $this->pipeItems(isset($data['documents']) ? $data['documents'] : ''),
'names' => $this->csvItems(isset($data['names']) ? $data['names'] : ''),
);
}
return parent::parseValue($type, $value);
}
public function serializeValue($type, $value)
{
$type = (string) $type;
if (!is_array($value)) {
return (string) $value;
}
if ($type === 'tags') {
return $this->wrapPipeArray(isset($value['tags']) ? $value['tags'] : array());
}
if (in_array($type, array('doc_from_rub_check', 'doc_from_rub_search'), true)) {
return $this->wrapPipeArray(isset($value['document_ids']) ? $value['document_ids'] : array());
}
if (in_array($type, array('analoque', 'teasers'), true)) {
$ids = isset($value['document_ids']) && is_array($value['document_ids']) ? $value['document_ids'] : array();
return implode(',', $this->cleanItems($ids));
}
if ($type === 'doc_from_rub') {
return isset($value['document_id']) ? (string) (int) $value['document_id'] : '';
}
if ($type === 'catalog') {
$catalogIds = isset($value['catalog_ids']) && is_array($value['catalog_ids']) ? $value['catalog_ids'] : array();
$documentIds = isset($value['document_ids']) && is_array($value['document_ids']) ? $value['document_ids'] : array();
$names = isset($value['names']) && is_array($value['names']) ? $value['names'] : array();
$data = array(
'catalogs' => implode(',', $this->cleanItems($catalogIds)),
'documents' => $this->wrapPipeArray($documentIds),
);
if (!empty($names)) {
$data['names'] = implode(',', $this->cleanItems($names));
}
return serialize($data);
}
return parent::serializeValue($type, $value);
}
public function normalizeDefault($type, $value)
{
$type = (string) $type;
if (in_array($type, array('tags', 'doc_from_rub_check', 'doc_from_rub_search'), true)) {
return $this->pipeWrappedList($value);
}
if ($type === 'analoque') {
return $this->commaList($value);
}
if ($type === 'catalog') {
return $this->commaList($value);
}
if ($type === 'doc_from_rub') {
$value = trim((string) $value);
return $value === '' ? '' : (string) (0 + $value);
}
return (string) $value;
}
public function renderEdit(array $field)
{
$kind = isset($field['editor_kind']) ? (string) $field['editor_kind'] : 'textarea';
$parsed = $this->value($field);
if ($kind === 'relation') {
$type = isset($field['rubric_field_type']) ? (string) $field['rubric_field_type'] : '';
$isDocFromRub = ($type === 'doc_from_rub');
$rubricIds = isset($field['relation_rubric_ids']) && is_array($field['relation_rubric_ids'])
? array_filter(array_map('intval', $field['relation_rubric_ids']))
: array();
$rubric = $isDocFromRub ? implode(',', $rubricIds) : '';
if ($isDocFromRub) {
// Значение показываем только если у документа реально сохранён выбор,
// иначе parsed['document_id'] равен id рубрики из default.
$val = ((int) (isset($field['document_field_id']) ? $field['document_field_id'] : 0) > 0 && is_array($parsed))
? (string) (int) (isset($parsed['document_id']) ? $parsed['document_id'] : 0)
: '';
} else {
$val = is_array($parsed) ? '' : (string) $parsed;
}
$rubricAttr = $rubric !== '' ? ' data-document-picker-rubric="' . $this->e($rubric) . '"' : '';
$hint = ($isDocFromRub && $rubric !== '')
? 'Только документы из рубрики #' . $this->e($rubric) . '.'
: '';
$hasValue = ($val !== '' && (int) $val > 0);
$selected = isset($field['relation_selected']) && is_array($field['relation_selected']) ? $field['relation_selected'] : array();
$title = $hasValue
? (isset($selected['title']) && $selected['title'] !== '' ? (string) $selected['title'] : ('Документ #' . (int) $val))
: 'Документ не выбран';
$idLabel = $hasValue ? '#' . (int) $val : 'нажмите, чтобы выбрать';
return '
'
. ''
. ''
. ''
. '
' . $hint;
}
if ($kind === 'relation_list') {
return $this->renderRelationList($field);
}
if (in_array($kind, array('lines', 'key_value', 'pipe_list'), true)) {
$val = is_array($parsed) ? '' : (string) $parsed;
$hint = in_array($kind, array('key_value', 'pipe_list'), true)
? 'Одна строка на запись, части разделяются символом |.'
: 'Одна строка на значение.';
return ''
. '' . $hint . '';
}
return $this->renderGeneric($field);
}
protected function renderRelationList(array $field)
{
$type = isset($field['rubric_field_type']) ? (string) $field['rubric_field_type'] : '';
$rubricIds = isset($field['relation_rubric_ids']) && is_array($field['relation_rubric_ids'])
? array_values(array_filter(array_map('intval', $field['relation_rubric_ids'])))
: array();
$rubric = in_array($type, array('doc_from_rub_check', 'doc_from_rub_search'), true)
? implode(',', $rubricIds)
: '';
$items = isset($field['relation_items']) && is_array($field['relation_items']) ? $field['relation_items'] : array();
$tokens = '';
foreach ($items as $item) {
$id = isset($item['id']) ? (int) $item['id'] : 0;
if ($id <= 0) { continue; }
$title = isset($item['title']) ? (string) $item['title'] : ('Документ #' . $id);
$tokens .= ''
. '' . $this->e($title) . '#' . $id . ''
. ''
. ''
. '';
}
$rubricAttr = $rubric !== '' ? ' data-document-picker-rubric="' . $this->e($rubric) . '"' : '';
return ''
. '
'
. '
' . $tokens . '
'
. '
Документы не выбраны.
'
. '
'
. '
';
}
protected function definitions()
{
return array(
'tags' => array(
'title' => 'Теги',
'icon' => 'tags',
'summary' => 'Набор тегов документа.',
'default_label' => 'Теги по умолчанию',
'default_hint' => 'Один тег на строку.',
'default_kind' => 'lines',
'entity_fields' => array('tags[]'),
),
'doc_from_rub' => array(
'title' => 'Документ из рубрики',
'icon' => 'file-export',
'summary' => 'Связь с одним документом из выбранной рубрики.',
'default_label' => 'Документ по умолчанию',
'default_hint' => 'ID документа или пусто.',
'default_kind' => 'relation',
'entity_fields' => array('document_id'),
),
'doc_from_rub_all' => array(
'title' => 'Документы из рубрики',
'icon' => 'files',
'summary' => 'Вывод всех связанных документов из рубрики.',
'default_label' => 'Значение по умолчанию',
'default_hint' => 'Обычно пусто; выбор задаётся настройками legacy-поля.',
'default_kind' => 'textarea',
'entity_fields' => array('rubric_id', 'template'),
),
'doc_from_rub_check' => array(
'title' => 'Документы из рубрики с выбором',
'icon' => 'checks',
'summary' => 'Несколько связанных документов, legacy хранит выбранные ID через |.',
'default_label' => 'ID документов по умолчанию',
'default_hint' => 'Один ID на строку.',
'default_kind' => 'relation_list',
'entity_fields' => array('document_ids[]'),
),
'doc_from_rub_search' => array(
'title' => 'Поиск документов из рубрики',
'icon' => 'file-search',
'summary' => 'Связанные документы с поиском, legacy хранит выбранные ID через |.',
'default_label' => 'ID документов по умолчанию',
'default_hint' => 'Один ID на строку.',
'default_kind' => 'relation_list',
'entity_fields' => array('document_ids[]'),
),
'catalog' => array(
'title' => 'Каталог',
'icon' => 'building-store',
'summary' => 'Связь с legacy-модулем catalog.',
'default_label' => 'Каталоги по умолчанию',
'default_hint' => 'Один ID каталога на строку. Legacy сохранение документов использует сериализованный массив.',
'default_kind' => 'lines',
'entity_fields' => array('catalog_ids[]', 'document_ids[]', 'names[]'),
),
'analoque' => array(
'title' => 'Аналоги / вместе с товаром',
'icon' => 'relation-one-to-many',
'summary' => 'Список связанных документов. Legacy-данные встречаются как ID через запятую, JSON или serialize.',
'default_label' => 'ID документов по умолчанию',
'default_hint' => 'Один ID на строку или через запятую.',
'default_kind' => 'relation_list',
'entity_fields' => array('document_ids[]'),
),
'teasers' => array(
'title' => 'Тизеры документов',
'icon' => 'layout-list',
'summary' => 'Упорядоченный список документов, которые выводятся как тизеры.',
'default_label' => 'Документы по умолчанию',
'default_hint' => 'Один ID документа на строку или через запятую.',
'default_kind' => 'relation_list',
'entity_fields' => array('document_ids[]'),
),
);
}
protected function pipeItems($value)
{
return $this->cleanItems(explode('|', (string) $value));
}
protected function csvItems($value)
{
return $this->cleanItems(explode(',', (string) $value));
}
protected function wrapPipeArray(array $items)
{
$items = $this->cleanItems($items);
return !empty($items) ? '|' . implode('|', $items) . '|' : '';
}
protected function documentIds($value)
{
if (is_array($value)) {
$items = $this->flatten($value);
$value = implode(',', $items);
} else {
$value = trim((string) $value);
}
if ($value === '') {
return array();
}
$items = array($value);
$decoded = $this->structuredValue($value);
if (is_array($decoded)) {
$items = $this->flatten($decoded);
}
$ids = array();
foreach ($items as $item) {
foreach (preg_split('/[,\s]+/', (string) $item) ?: array() as $part) {
$part = trim((string) $part);
if ($part === '') {
continue;
}
if (strpos($part, '|') !== false) {
$bits = explode('|', $part);
$part = trim((string) end($bits));
}
if (ctype_digit($part) && (int) $part > 0) {
$ids[] = (string) (int) $part;
}
}
}
return array_values(array_unique($ids));
}
protected function flatten($value)
{
$out = array();
if (is_array($value)) {
foreach ($value as $item) {
$out = array_merge($out, $this->flatten($item));
}
return $out;
}
return array((string) $value);
}
protected function cleanItems(array $items)
{
$out = array();
foreach ($items as $item) {
$item = trim((string) $item);
if ($item !== '') {
$out[] = $item;
}
}
return array_values($out);
}
}