Skip to content
Closed
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
21 changes: 21 additions & 0 deletions assets/content-builder.css
Original file line number Diff line number Diff line change
Expand Up @@ -1381,3 +1381,24 @@ body.modal-open {
overflow-wrap: break-word;
text-align: left;
}

/* Legacy HTML Migration */
.yform-content-builder .content-builder-legacy {
margin-bottom: 15px;
}

.yform-content-builder .content-builder-legacy-notice {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}

.yform-content-builder .content-builder-legacy-notice-text {
flex: 1 1 auto;
}

.yform-content-builder .content-builder-legacy-notice .content-builder-legacy-migrate {
flex: 0 0 auto;
margin-left: auto;
}
123 changes: 123 additions & 0 deletions assets/content-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,33 @@
this.initMoveButtons();
this.initGridViews();
this.updateSectionClasses();
this.initLegacyEditors();
},

/**
* Legacy-HTML-Editor initialisieren (CKE5 oder TinyMCE) in Feldern,
* deren bisheriger Wert klassisches HTML statt Content-Builder-JSON enthielt.
*/
initLegacyEditors: function() {
$('.yform-content-builder .content-builder-legacy').each(function() {
var $wrap = $(this);
if ($wrap.data('legacyInitialized')) {
return;
}
$wrap.data('legacyInitialized', true);

var editor = ($wrap.data('legacyEditor') || '').toString();
var $textarea = $wrap.find('textarea.content-builder-legacy-textarea').first();
if ($textarea.length === 0) {
return;
}

if (editor === 'cke5' && typeof cke5_init === 'function') {
try { cke5_init($textarea); } catch (e) { /* noop */ }
} else if (editor === 'tinymce' && typeof tiny_init === 'function') {
try { tiny_init($wrap); } catch (e) { /* noop */ }
}
});
},

initElementMenuTooltips: function() {
Expand Down Expand Up @@ -72,6 +99,14 @@
bindEvents: function() {
var self = this;

// Legacy-HTML → Content Builder migrieren
$(document).on('click', '.content-builder-legacy-migrate', function(e) {
e.preventDefault();
e.stopPropagation();
self.migrateLegacyHtml($(this).closest('.yform-content-builder'));
return false;
});

// Slice löschen - MUSS VOR edit kommen!
$(document).on('click', '.btn-slice-delete', function(e) {
e.preventDefault();
Expand Down Expand Up @@ -1376,6 +1411,94 @@
}, 500);
},

/**
* Konvertiert einen klassischen HTML-Wert in einen einzelnen
* starter_text-Slice und initialisiert den Content Builder.
*/
migrateLegacyHtml: function($container) {
if ($container.length === 0) {
return;
}

var $legacy = $container.find('.content-builder-legacy').first();
if ($legacy.length === 0) {
return;
}

var $textarea = $legacy.find('textarea.content-builder-legacy-textarea').first();
if ($textarea.length === 0) {
return;
}

// Aktuellen HTML-Inhalt holen (TinyMCE / CKE5 in Textarea zurueckschreiben).
var html = '';
var textareaId = $textarea.attr('id');

if (typeof tinymce !== 'undefined' && textareaId) {
var tinyEd = tinymce.get(textareaId);
if (tinyEd) {
try {
tinyEd.save();
html = tinyEd.getContent();
} catch (e) { /* noop */ }
}
}

if (!html && typeof ckeditors !== 'undefined' && textareaId && ckeditors[textareaId]) {
try { html = ckeditors[textareaId].getData(); } catch (e) { /* noop */ }
}

if (!html) {
html = $textarea.val() || '';
}

var $confirmBtn = $legacy.find('.content-builder-legacy-migrate');
var confirmMsg = $confirmBtn.data('confirm');
if (confirmMsg && !window.confirm(confirmMsg)) {
return;
}

// Editor-Instanzen sauber entfernen.
if (typeof tinymce !== 'undefined' && textareaId) {
var ed = tinymce.get(textareaId);
if (ed) { try { ed.remove(); } catch (e) { /* noop */ } }
}
if (typeof ckeditors !== 'undefined' && textareaId && ckeditors[textareaId]) {
try { ckeditors[textareaId].destroy(); } catch (e) { /* noop */ }
}

// Form-Input-Name vom Legacy-Textarea uebernehmen, damit der
// submit ohne weitere Aktion den Content-Builder-JSON sendet.
var fieldName = $textarea.attr('name') || '';

// Legacy-Block entfernen.
$legacy.remove();

// Versteckten Content-Builder-Datenspeicher (neu) anlegen.
var $hidden = $container.find('input.content-builder-data');
if ($hidden.length === 0 && fieldName) {
$hidden = $('<input type="hidden" class="content-builder-data">').attr('name', fieldName);
$container.append($hidden);
}

// Container fuer Slices/Add-Button wieder anzeigen.
$container.find('.content-builder-slices, .content-builder-add').show();
$container.removeClass('has-legacy-html');

// Einen starter_text-Slice mit dem HTML als "text"-Feld anlegen.
var $slicesContainer = $container.find('.content-builder-slices').first();
this.addSlice($slicesContainer, 'starter_text', 'Text');

var $newSlice = $slicesContainer.children('.content-builder-slice').last();
if ($newSlice.length) {
var sliceData = { text: html };
$newSlice.attr('data-slice-data', JSON.stringify(sliceData));
}

// Hidden-Feld mit dem neuen Slice befuellen.
this.updateHiddenField();
},

updateHiddenField: function() {
$('.yform-content-builder').each(function() {
var $container = $(this);
Expand Down
9 changes: 9 additions & 0 deletions lang/de_de.lang
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ yform_content_builder_enable_online_toggle = Online/Offline pro Abschnitt
yform_content_builder_enable_online_toggle_label = Online/Offline-Schalter für einzelne Abschnitte aktivieren
yform_content_builder_enable_online_toggle_notice = Wenn aktiv, erscheint in der Slice-Toolbar ein Augen-Button, mit dem einzelne Abschnitte offline geschaltet werden können. Offline-Abschnitte bleiben in den Daten erhalten, werden aber im Frontend (via yform_content_builder_helper) automatisch ausgeblendet. Gilt nur für die YForm-Variante – die Modulvariante nutzt die normalen REDAXO-Slice-Funktionen.

yform_content_builder_legacy_editor = Legacy-HTML-Editor
yform_content_builder_legacy_editor_none = Deaktiviert
yform_content_builder_legacy_editor_notice = Editor, der angezeigt wird, wenn ein bestehender Wert klassisches HTML statt Content-Builder-JSON enthält. Erlaubt die Migration alter Inhalte in den Content Builder.
yform_content_builder_legacy_profile = Legacy-Editor-Profil
yform_content_builder_legacy_profile_notice = Profilname (Attribute) für CKEditor 5 oder TinyMCE, das für den Legacy-Editor verwendet wird.
yform_content_builder_legacy_notice = Der bestehende Inhalt liegt im klassischen HTML-Format vor. Auf den Content Builder umstellen?
yform_content_builder_legacy_migrate = Jetzt in Content Builder umwandeln
yform_content_builder_legacy_migrate_confirm = Den vorhandenen HTML-Inhalt jetzt als Text-Element in den Content Builder übernehmen?

yform_content_builder_element_add = Element hinzufügen
yform_content_builder_element_edit = Bearbeiten
yform_content_builder_element_delete = Löschen
Expand Down
9 changes: 9 additions & 0 deletions lang/en_gb.lang
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ yform_content_builder_enable_online_toggle = Online/Offline per section
yform_content_builder_enable_online_toggle_label = Enable online/offline switch for individual sections
yform_content_builder_enable_online_toggle_notice = When enabled, an eye button appears in the slice toolbar that allows individual sections to be set offline. Offline sections are preserved in the data but automatically hidden in the frontend (via yform_content_builder_helper). Applies only to the YForm variant – the module variant uses the standard REDAXO slice functions.

yform_content_builder_legacy_editor = Legacy HTML editor
yform_content_builder_legacy_editor_none = Disabled
yform_content_builder_legacy_editor_notice = Editor shown when an existing value contains classic HTML instead of Content Builder JSON. Allows migrating legacy content into the Content Builder.
yform_content_builder_legacy_profile = Legacy editor profile
yform_content_builder_legacy_profile_notice = Profile name (attribute) for CKEditor 5 or TinyMCE used for the legacy editor.
yform_content_builder_legacy_notice = The existing value is classic HTML. Switch to the Content Builder?
yform_content_builder_legacy_migrate = Convert to Content Builder
yform_content_builder_legacy_migrate_confirm = Move the existing HTML content into the Content Builder as a text element now?

yform_content_builder_empty = No elements yet. Add an element below.
yform_content_builder_delete_confirm = Really delete element?

Expand Down
50 changes: 49 additions & 1 deletion lib/rex_yform_value_content_builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -980,7 +980,37 @@ protected function getTemplateVars(): array
{
$value = $this->parseValue();
$framework = $this->getElement('framework', 'bootstrap');


// Legacy-HTML-Erkennung:
// Wenn der gespeicherte Wert nicht leer ist aber keinen Slice-Array ergibt,
// gehen wir davon aus, dass es sich um klassisches HTML handelt und
// bieten dem Admin die Migration in den Content Builder an.
$rawValue = (string) $this->getValue();
$legacyHtml = '';
if ($rawValue !== '' && empty($value)) {
$decoded = json_decode($rawValue, true);
if (!is_array($decoded)) {
$legacyHtml = $rawValue;
}
}

$addon = rex_addon::get('yform_content_builder');
$legacyEditor = (string) $this->getElement('legacy_editor', '');
if ($legacyEditor === '') {
$legacyEditor = (string) $addon->getConfig('legacy_editor', 'none');
}
if (!in_array($legacyEditor, ['none', 'cke5', 'tinymce'], true)) {
$legacyEditor = 'none';
}

$legacyProfile = (string) $this->getElement('legacy_profile', '');
if ($legacyProfile === '') {
$legacyProfile = (string) $addon->getConfig('legacy_profile', 'default');
}
if ($legacyProfile === '') {
$legacyProfile = 'default';
}

return [
'value' => $value,
'field_type' => 'content_builder',
Expand All @@ -993,6 +1023,9 @@ protected function getTemplateVars(): array
'description' => $this->getElement('description', ''),
'framework' => $framework,
'available_elements' => $this->getAvailableElements(),
'legacy_html' => $legacyHtml,
'legacy_editor' => $legacyEditor,
'legacy_profile' => $legacyProfile,
];
}

Expand Down Expand Up @@ -1281,6 +1314,21 @@ public function getDefinitions(): array
'expanded' => false,
'default' => '',
],
'legacy_editor' => [
'type' => 'choice',
'label' => 'Legacy-HTML-Editor (leer = Addon-Einstellung)',
'choices' => [
'' => '-- Addon-Einstellung --',
'none' => 'Deaktiviert',
'cke5' => 'CKEditor 5',
'tinymce' => 'TinyMCE',
],
'default' => '',
],
'legacy_profile' => [
'type' => 'text',
'label' => 'Legacy-Editor-Profil (leer = Addon-Einstellung)',
],
'description' => ['type' => 'text', 'label' => 'Beschreibung'],
'notice' => ['type' => 'text', 'label' => rex_i18n::msg('yform_values_defaults_notice')],
],
Expand Down
37 changes: 37 additions & 0 deletions pages/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@
$addon->setConfig('theme', rex_post('theme', 'string', ''));
$addon->setConfig('compact_mode', rex_post('compact_mode', 'bool', false));
$addon->setConfig('enable_online_toggle', rex_post('enable_online_toggle', 'bool', false));

$allowedLegacyEditors = ['none', 'cke5', 'tinymce'];
$legacyEditor = rex_post('legacy_editor', 'string', 'none');
if (!in_array($legacyEditor, $allowedLegacyEditors, true)) {
$legacyEditor = 'none';
}
$addon->setConfig('legacy_editor', $legacyEditor);
$addon->setConfig('legacy_profile', rex_post('legacy_profile', 'string', 'default'));

echo rex_view::success(rex_i18n::msg('yform_content_builder_settings_saved'));

// Theme Builder Cache zurücksetzen
Expand All @@ -30,6 +39,11 @@
$currentTheme = $addon->getConfig('theme', '');
$compactMode = $addon->getConfig('compact_mode', false);
$enableOnlineToggle = $addon->getConfig('enable_online_toggle', false);
$legacyEditor = $addon->getConfig('legacy_editor', 'none');
if (!in_array($legacyEditor, ['none', 'cke5', 'tinymce'], true)) {
$legacyEditor = 'none';
}
$legacyProfile = (string) $addon->getConfig('legacy_profile', 'default');

// Formular bauen
$content = '';
Expand Down Expand Up @@ -64,6 +78,29 @@
$n['note'] = rex_i18n::msg('yform_content_builder_enable_online_toggle_notice');
$formElements[] = $n;

// Legacy-Editor (HTML-Erkennung)
$legacyEditorChoices = [
'none' => rex_i18n::msg('yform_content_builder_legacy_editor_none'),
'cke5' => 'CKEditor 5',
'tinymce' => 'TinyMCE',
];
$n = [];
$n['label'] = '<label for="legacy_editor">' . rex_i18n::msg('yform_content_builder_legacy_editor') . '</label>';
$n['field'] = '<select class="form-control" id="legacy_editor" name="legacy_editor">';
foreach ($legacyEditorChoices as $value => $label) {
$selected = ($value === $legacyEditor) ? ' selected' : '';
$n['field'] .= '<option value="' . rex_escape($value) . '"' . $selected . '>' . rex_escape($label) . '</option>';
}
$n['field'] .= '</select>';
$n['note'] = rex_i18n::msg('yform_content_builder_legacy_editor_notice');
$formElements[] = $n;

$n = [];
$n['label'] = '<label for="legacy_profile">' . rex_i18n::msg('yform_content_builder_legacy_profile') . '</label>';
$n['field'] = '<input type="text" class="form-control" id="legacy_profile" name="legacy_profile" value="' . rex_escape($legacyProfile) . '">';
$n['note'] = rex_i18n::msg('yform_content_builder_legacy_profile_notice');
$formElements[] = $n;

$fragment = new rex_fragment();
$fragment->setVar('elements', $formElements, false);
$content .= $fragment->parse('core/form/form.php');
Expand Down
Loading