diff --git a/.gitignore b/.gitignore index 62099da2..c5d8e9fc 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ vendor/ .phpunit.result.cache .php-cs-fixer.cache +/.idea/* diff --git a/plugins/manager/assets/history.js b/plugins/manager/assets/history.js new file mode 100644 index 00000000..6385bfa6 --- /dev/null +++ b/plugins/manager/assets/history.js @@ -0,0 +1,14 @@ + +$(document).on('rex:ready', function (event, container) { + container.find('#rex-yform-history-modal').on('shown.bs.modal', function () { + // init tooltips + container.find('[data-toggle="tooltip"]').tooltip({ + html: true + }); + + // history restore confirm dialog + $("#yform-manager-history-restore").on("click", function() { + return confirm($(this).attr('data-confirm-text')); + }); + }); +}); \ No newline at end of file diff --git a/plugins/manager/assets/manager.css b/plugins/manager/assets/manager.css index 6dd26e92..c3e475cb 100644 --- a/plugins/manager/assets/manager.css +++ b/plugins/manager/assets/manager.css @@ -287,3 +287,124 @@ body.rex-theme-dark .yform-be-relation-wrapper > div { background: #1a3332 } } + +#rex-yform-history-modal.in { + display: flex !important; +} + +#rex-yform-history-modal header.panel-heading:not(:first-of-type) { + margin-top: 3px; +} + +#rex-yform-history-modal header.panel-heading .panel-title i { + display: inline-block; + width: 25px; +} + +#rex-yform-history-modal header + .panel-collapse { + padding-top: 5px; +} + +.history-list tr.current-dataset-row { + background-color: #a0e4c1; +} + +.history-list .current-dataset-cell { + text-align: center; +} + +.history-list .current-dataset-hint { + color: #5bb585; + font-style: italic; +} + +.history-diff-table tbody td { + line-height: 1.5 +} + +.history-diff-table ins, +.history-diff-table .diff { + padding: 1px 4px; + border-radius: 3px; +} + +.history-diff-table del { + color: #d68787; +} + +.history-diff-table ins { + background-color: #a6e1a6; + text-decoration: none; +} + +.history-diff-table .diff { + background-color: #c6daff; +} + +.history-diff-table thead th:first-child { + padding-left: 10px; +} + +.history-diff-table tbody th:first-child { + padding-left: 45px; + position: relative; +} + +.history-diff-table tbody th:first-child > i, +.history-diff-table tbody th:first-child > span { + display: flex; + background-color: #F5F5F5; + width: 35px; + text-align: left; + position: absolute; + left: 0; + top: 1px; + bottom: 1px; + justify-content: center; + padding-top: 12px; + color: #000; +} + +/* special for numbers */ +.history-diff-table tbody th:first-child > i.rex-icon.fa-1:before { + content: '123'; +} + +.history-diff-table tbody th:first-child > span:before { + content: ''; + display: block; + width: 23px; + height: 14px; + border: 1px solid #000; + border-radius: 3px; +} + +.history-diff-table tbody th:first-child > span:after { + content: 'abc'; + font-size: 8px; + position: absolute; + top: 15px; + left: 9px; + font-weight: bold; +} + +#collapse-history-table-added .history-diff-table tbody th:first-child { + color: #088808; +} + +#collapse-history-table-removed .history-diff-table tbody th:first-child { + color: #e30d0d; +} + +#collapse-history-table-added .history-diff-table tbody td em, +#collapse-history-table-removed .history-diff-table tbody td em { + color: #AAA; +} + +@media (min-width: 768px) { + #rex-yform-history-modal.in .modal-dialog { + max-width: calc(100% - 40px); + width: auto; + display: inline-block; + } +} diff --git a/plugins/manager/boot.php b/plugins/manager/boot.php index 670fa77b..bd794482 100644 --- a/plugins/manager/boot.php +++ b/plugins/manager/boot.php @@ -30,6 +30,7 @@ rex_view::addJsFile($this->getAssetsUrl('relations.js')); rex_view::addCssFile($this->getAssetsUrl('manager.css')); rex_view::addJsFile($this->getAssetsUrl('widget.js')); + rex_view::addJsFile($this->getAssetsUrl('history.js')); if (!rex::getUser()->isAdmin()) { $page = $this->getProperty('page'); diff --git a/plugins/manager/fragments/yform/manager/history.diff.php b/plugins/manager/fragments/yform/manager/history.diff.php new file mode 100644 index 00000000..20b08d36 --- /dev/null +++ b/plugins/manager/fragments/yform/manager/history.diff.php @@ -0,0 +1,199 @@ +getVar('history_id', null); +$datasetId = $this->getVar('dataset_id', null); +$currentDataset = $this->getVar('current_dataset', null); + +/** @var rex_yform_manager_table|null $table */ +$table = $this->getVar('table', null); + +$sql = rex_sql::factory(); +$timestamp = $sql->setQuery(sprintf('SELECT `timestamp` FROM %s WHERE id = :id', rex::getTable('yform_history')), [':id' => $historyId])->getValue('timestamp'); + +$data = $sql->getArray(sprintf('SELECT * FROM %s WHERE history_id = :history_id', rex::getTable('yform_history_field')), [':history_id' => $historyId]); +$data = array_column($data, 'value', 'field'); + +if(null !== $historyId && null !== $table): + + $diffs = [ + 'changed' => ['icon' => 'fas fa-not-equal', 'count' => 0, 'rows' => ''], + 'added' => ['icon' => 'fas fa-layer-plus', 'count' => 0, 'rows' => ''], + 'removed' => ['icon' => 'far fa-layer-minus', 'count' => 0, 'rows' => ''], + 'unchanged' => ['icon' => 'fas fa-equals', 'count' => 0, 'rows' => ''], + // 'prev_changed' => ['icon' => 'fas fa-not-equal', 'count' => 0, 'rows' => ''], + ]; + + $fieldsInDataset = []; + + $tableFields = $table->getFields(); + + foreach ($table->getValueFields() as $field) { + if (!array_key_exists($field->getName(), $data)) { + if($currentDataset->hasValue($field->getName())) { + ++$diffs['added']['count']; + $diffs['added']['rows'] .= ' + + ' . rex_yform_history_helper::getFieldTypeIcon($field) . ' ' . $field->getLabel() . ' + ' . rex_yform_history_helper::getFieldValue($field, $currentDataset, $table) . ' + ' . rex_i18n::msg('yform_history_diff_not_yet_existing') . ' + '; + } + + continue; + } + + $change = 'unchanged'; + + $fieldsInDataset[] = $field->getName(); + $historyValue = $data[$field->getName()]; + $currentValue = ($currentDataset->hasValue($field->getName()) ? $currentDataset->getValue($field->getName()) : '-'); + + $class = 'rex_yform_value_' . $field->getTypeName(); + + // count diffs + if(!$currentDataset->hasValue($field->getName())) { + $change = 'removed'; + } elseif('' . $historyValue !== '' . $currentValue) { + $change = 'changed'; + } + + ++$diffs[$change]['count']; + + if (is_callable([$class, 'getListValue']) && !in_array($field->getTypeName(), ['text', 'textarea'], true)) { + // to ensure correct replacement with list value, ensure datatype by current dataset + if(gettype($currentValue) !== gettype($historyValue)) { + settype($historyValue, gettype($currentValue)); + } + + // get (formatted) value for history entry + $historyValue = $class::getListValue([ + 'value' => $historyValue, + 'subject' => $historyValue, + 'field' => $field->getName(), + 'params' => [ + 'field' => $field->toArray(), + 'fields' => $this->table->getFields(), + ], + ]); + + // get (formatted) value for current entry + if($currentDataset->hasValue($field->getName())) { + $currentValue = $class::getListValue([ + 'value' => $currentValue, + 'subject' => $currentValue, + 'field' => $field->getName(), + 'params' => [ + 'field' => $field->toArray(), + 'fields' => $tableFields, + ], + ]); + } else { + $currentValue = '-'; + } + } else { + $historyValue = rex_escape($historyValue); + $currentValue = rex_escape($currentValue); + } + + // diff values for specific fields + if('changed' === $change) { + switch($field->getTypeName()) { + case 'text': + case 'textarea': + $diff = rex_yform_history_helper::diffStringsToHtml($currentValue, $historyValue); + $historyValue = $diff; + break; + + default: + if($historyValue !== $currentValue) { + $historyValue = '' . $historyValue . ''; + } + break; + } + } + + $diffs[$change]['rows'] .= ' + + ' . rex_yform_history_helper::getFieldTypeIcon($field) . ' ' . $field->getLabel() . ' + ' . $currentValue . ' + ' . $historyValue . ' + '; + + // remove field from dataset to collect meanwhile deleted fields + unset($data[$field->getName()]); + } + +foreach ($data as $field => $value) { + ++$diffs['removed']['count']; + $diffs['removed']['rows'] .= ' + + ' . $field . ' + ' . rex_i18n::msg('yform_history_diff_no_longer_existing') . ' + ' . $value . ' + '; +} + +// build restore url +$restoreUrl = http_build_query([ + 'table_name' => $table->getTableName(), + 'func' => 'history', + 'subfunc' => 'restore', + 'data_id' => $datasetId, + 'history_id' => $historyId, +]) . http_build_query(rex_csrf_token::factory($this->getVar('csrf_key', ''))->getUrlParams()); + +$content = ' + + + +'; + +echo $content; +endif; diff --git a/plugins/manager/lang/de_de.lang b/plugins/manager/lang/de_de.lang index 9dcc164b..11bf904c 100644 --- a/plugins/manager/lang/de_de.lang +++ b/plugins/manager/lang/de_de.lang @@ -1,5 +1,3 @@ -yform_function_button = Aktion - yform_manager = Table Manager navigation_manager = Tabellen @@ -14,7 +12,7 @@ yform_errors_occurred = Es sind Fehler aufgetreten yform_help_empty = Leereinträge können mit
"(empty)" gesucht werden yform_help_multipleselect = Mehrfachauswahl mit gedrückter CTRL/CMD Taste yform_updatetable_with_delete = mit Feldlöschung -yform_updatetable_with_delete_confirm = Wirklich aktualisieren mit Feldlöschung ? +yform_updatetable_with_delete_confirm = Wirklich aktualisieren mit Feldlöschung? yform_relation_move_first_data = Ausgewählten Datensatz an den Anfang verschieben yform_relation_move_up_data = Ausgewählten Datensatz nach oben verschieben @@ -96,6 +94,7 @@ yform_manager_table_features = Aktivierte Optionen yform_manager_type_id = Typ yform_manager_type_name = Typname +yform_manager_type_unknown = unbekannt yform_manager_tableset = Tableset yform_manager_table = Tabelle @@ -237,8 +236,10 @@ yform_manager_import_error_duplicatefielddefinition = Es gibt Felder, welche ide # history yform_history = Historie +yform_history_title = Historie für yform_history_dataset_id = Datensatz-ID yform_history_dataset = Datensatz +yform_history_dataset_current = Aktuelle Version yform_history_action = Aktion yform_history_action_create = erstellt yform_history_action_update = bearbeitet @@ -247,6 +248,7 @@ yform_history_user = Benutzer yform_history_timestamp = Zeitstempel yform_history_view = ansehen yform_history_restore = zurücksetzen +yform_history_restore_confirm = Wirklich auf diese Version zurücksetzen? yform_history_restore_this = Datensatz auf diese Version zurücksetzen yform_history_restore_success = Datensatz wurde zurückgesetzt. yform_history_restore_error = Datensatz konnte nicht zurückgesetzt werden. @@ -255,6 +257,20 @@ yform_history_delete_all = komplett yform_history_delete_older_3_months = älter als 3 Monate yform_history_delete_confirm = Historie wirklich löschen? yform_history_delete_success = Die Historien-Datensätze wurden gelöscht. +yform_history_revision = Revision/Änderung +yform_history_dataset_missing = Es wurde keine (gültige) Datensatz-ID übergeben! Der Vergleich mit einem ausgewählten Eintrag der Historie ist daher nicht möglich. +yform_history_diff_headline_changed = Geänderte Werte +yform_history_diff_headline_unchanged = Unveränderte Werte +yform_history_diff_headline_added = Hinzugefügte Felder +yform_history_diff_headline_removed = Entfernte Felder +yform_history_diff_headline_prev_changed = Änderungen gegenüber vorheriger Version +yform_history_diff_to_current = Änderungen (zu aktuell) +yform_history_diff_to_previous = Änderungen (zu vorherig) +yform_history_diff_added = neu +yform_history_diff_removed = entfernt +yform_history_diff_not_yet_existing = noch nicht vorhanden +yform_history_diff_no_longer_existing = nicht mehr vorhanden +yform_history_is_current_dataset = aktuelle Version des Datensatzes yform_manager_show_form_notation = Formular-Code diff --git a/plugins/manager/lang/en_gb.lang b/plugins/manager/lang/en_gb.lang index 24dfbea8..87045472 100644 --- a/plugins/manager/lang/en_gb.lang +++ b/plugins/manager/lang/en_gb.lang @@ -94,6 +94,7 @@ yform_manager_table_features = Activated options yform_manager_type_id = Type yform_manager_type_name = Type name +yform_manager_type_unknown = unknown yform_manager_tableset = Table set yform_manager_table = Table @@ -128,6 +129,9 @@ yform_manager_table_hidden = Visibility yform_hidden = hidden yform_visible = visible +yform_manager_actions_all = all actions +yform_manager_users_all = all users +yform_manager_history_date_notice = all entries before and equal to this date are displayed # edit.inc.php yform_thankyouforupdate = Record updated. @@ -166,9 +170,11 @@ yform_dataset_delete = Delete result reset yform_dataset_deleted = Result set deleted yform_dataset_delete_confirm = Delete result set? -yform_data = Record -yform_datas = Records -yform_manager_search = Search +yform_dataset_edit_confirm = Should all selected data records really be edited? + +yform_data = data record +yform_datas = data records +yform_manager_search = search yform_data_view = show @@ -230,8 +236,10 @@ yform_manager_import_error_duplicatefielddefinition = There are identical fields # history yform_history = History +yform_history_title = Historie for yform_history_dataset_id = Record ID yform_history_dataset = Record +yform_history_dataset_current = current version yform_history_action = Action yform_history_action_create = created yform_history_action_update = updated @@ -240,6 +248,7 @@ yform_history_user = User yform_history_timestamp = Timestamp yform_history_view = view yform_history_restore = revert +yform_history_restore_confirm = Really revert to this version? yform_history_restore_this = Revert record to this version yform_history_restore_success = Record reverted yform_history_restore_error = Record could not be reverted. @@ -248,6 +257,20 @@ yform_history_delete_all = completely yform_history_delete_older_3_months = older than 3 months yform_history_delete_confirm = Delete History? yform_history_delete_success = History records deleted. +yform_history_revision = revision/change +yform_history_dataset_missing = No (valid) data record ID was transferred! A comparison with a selected entry in the history is therefore not possible. +yform_history_diff_headline_changed = changed values +yform_history_diff_headline_unchanged = unchanged values +yform_history_diff_headline_added = added fields +yform_history_diff_headline_removed = removed fields +yform_history_diff_headline_prev_changed = changes compared to previous version +yform_history_diff_to_current = changes (to current) +yform_history_diff_to_previous = changes (to previous) +yform_history_diff_added = added +yform_history_diff_removed = removed +yform_history_diff_not_yet_existing = not yet existing +yform_history_diff_no_longer_existing = no longer existing +yform_history_is_current_dataset = current version of the record yform_manager_show_form_notation = Form code @@ -288,3 +311,13 @@ yform_will_set_to = will be yform_field_add = Add field yform_field_update = Update field yform_field_db_type = Database field type + +yform_manager_table_data_view_roles = Data view limited to +yform_manager_table_data_edit_roles = Data editing limited to + +yform_manager_table_nopermission = No permission for this table + +yform_manager_tables_edit = Tables with editing permission +yform_manager_tables_view = Tables with view permission + +yform_structure_article_could_not_be_deleted = The article could not be deleted because it is used by the following data records: diff --git a/plugins/manager/lang/es_es.lang b/plugins/manager/lang/es_es.lang index 3e2bc382..a30ba4c7 100644 --- a/plugins/manager/lang/es_es.lang +++ b/plugins/manager/lang/es_es.lang @@ -8,11 +8,11 @@ yform_alltables = Todas las tablas yform_back_to_overview = Volver al resumen yform_updatetable = Actualizar tabla (s) yform_tablesupdated = Table/n ha sido actualizado - +yform_errors_occurred = Se han producido errores yform_help_empty = Las entradas vacías se pueden buscar con
"(vacío)" yform_help_multipleselect = Selección múltiple con tecla CTRL/CMD presionada -yform_updatetable_with_delete = con eliminación de campo -yform_updatetable_with_delete_confirm = Esta seguro de actualizar con eliminación de campo? +yform_updatetable_with_delete = con eliminación de campos +yform_updatetable_with_delete_confirm = ¿Actualización real con eliminación de campos? yform_relation_move_first_data = Mueva el registro seleccionado al principio yform_relation_move_up_data = Mover el registro seleccionado hacia arriba @@ -91,6 +91,7 @@ yform_manager_table_features = Opciones activadas yform_manager_type_id = Tipo yform_manager_type_name = Nombre tipo +yform_manager_type_unknown = desconocido yform_manager_tableset = Tableset yform_manager_table = Tabla @@ -125,13 +126,18 @@ yform_manager_table_hidden = Visibilidad yform_hidden = Oculto yform_visible = Visible +yform_manager_actions_all = Todas las acciones +yform_manager_users_all = Todos los usuarios +yform_manager_history_date_notice = Se muestran todas las entradas anteriores e iguales a esta fecha # edit.inc.php yform_thankyouforupdate = Record ha sido actualizado. +yform_thankyouforupdates = Se han actualizado los registros de datos. yform_thankyouforentry = Registro fue ingresado yform_editdata = Editar registro [id: {0}] yform_editdata_collection = {0} editar registros yform_editdata_collection_error_abort = La transacción falló debido a un error: {0} +yform_clonedata = Clonar registro de datos [ID original: {0}] yform_adddata = Crear registro de datos yform_searchtext = Palabra clave yform_searchfields = Campos de tabla para buscar @@ -141,6 +147,8 @@ yform_function_button = Accion yform_add = Añadir yform_add_apply = Tomar el relevo yform_edit = Editar +yform_clone = Clon +yform_view = Mostrar yform_save = Guardar yform_save_apply = Tomar el relevo yform_delete = Borrar @@ -159,6 +167,8 @@ yform_dataset_delete = Eliminar resultado de datos yform_dataset_deleted = El resultado de los datos fue vaciado yform_dataset_delete_confirm = ¿Realmente vaciar el resultado de los datos? +yform_dataset_edit_confirm = ¿Deben procesarse realmente todos los registros de datos seleccionados? + yform_data = Registro yform_datas = Archivos yform_manager_search = Búsqueda @@ -223,8 +233,10 @@ yform_manager_import_error_duplicatefielddefinition = Hay campos que son idénti # history yform_history = historia +yform_history_title = Historia para yform_history_dataset_id = Identificación del registro yform_history_dataset = registro +yform_history_dataset_current = Versión actual yform_history_action = Acción yform_history_action_create = Creado yform_history_action_update = Editado @@ -233,6 +245,7 @@ yform_history_user = Usuario yform_history_timestamp = Fecha y hora yform_history_view = Vista yform_history_restore = Regreso +yform_history_restore_confirm = ¿Reiniciar realmente a esta versión? yform_history_restore_this = Restablecer registro a esta versión yform_history_restore_success = El registro ha sido reiniciado. yform_history_restore_error = El registro no se pudo restablecer. @@ -241,6 +254,20 @@ yform_history_delete_all = Completo yform_history_delete_older_3_months = Más de 3 meses yform_history_delete_confirm = ¿Realmente borras la historia? yform_history_delete_success = Los registros de historial han sido eliminados. +yform_history_revision = Revisión/modificación +yform_history_dataset_missing = No se ha transferido ningún ID de registro de datos (válido). Por lo tanto, no es posible realizar una comparación con una entrada seleccionada en el historial. +yform_history_diff_headline_changed = Valores modificados +yform_history_diff_headline_unchanged = Valores sin cambios +yform_history_diff_headline_added = Campos añadidos +yform_history_diff_headline_removed = Campos eliminados +yform_history_diff_headline_prev_changed = Cambios respecto a la versión anterior +yform_history_diff_to_current = Cambios (al actual) +yform_history_diff_to_previous = Cambios (al anterior) +yform_history_diff_added = nuevo +yform_history_diff_removed = eliminado +yform_history_diff_not_yet_existing = aún no disponible +yform_history_diff_no_longer_existing = ya no está disponible +yform_history_is_current_dataset = versión actual del registro. yform_manager_show_form_notation = Código del formulario @@ -281,3 +308,13 @@ yform_will_set_to = Deberá yform_field_add = Añadir campo yform_field_update = Campo de actualización yform_field_db_type = Tipo de campo de base de datos + +yform_manager_table_data_view_roles = Vista de datos limitada a +yform_manager_table_data_edit_roles = Tratamiento de datos limitado a + +yform_manager_table_nopermission = No hay autorización para esta tabla + +yform_manager_tables_edit = Tablas con autorización de edición +yform_manager_tables_view = Tablas con autorización de vista + +yform_structure_article_could_not_be_deleted = El artículo no se ha podido borrar porque lo utilizan los siguientes registros de datos: diff --git a/plugins/manager/lang/sv_se.lang b/plugins/manager/lang/sv_se.lang index 625e2f5b..ca834cf7 100644 --- a/plugins/manager/lang/sv_se.lang +++ b/plugins/manager/lang/sv_se.lang @@ -91,6 +91,7 @@ yform_manager_table_features = Aktiverade optioner yform_manager_type_id = Typ yform_manager_type_name = Typnamn +yform_manager_type_unknown = okänd yform_manager_tableset = Tabellset yform_manager_table = Tabell @@ -125,6 +126,9 @@ yform_manager_table_hidden = Synlighet yform_hidden = Dold yform_visible = Synligt +yform_manager_actions_all = Alla åtgärder +yform_manager_users_all = Alla användare +yform_manager_history_date_notice = Alla poster före och lika med detta datum visas # edit.inc.php yform_thankyouforupdate = Datasatsen aktualiserades. @@ -133,6 +137,7 @@ yform_thankyouforentry = Datasatsen tillfogades. yform_editdata = Redigera datasats yform_editdata_collection = Redigera {0} datasatser yform_editdata_collection_error_abort = Transaktionen misslyckades på grund av fel: {0} +yform_clonedata = Klonad datapost [Ursprungligt ID: {0}] yform_adddata = Skapa datasats yform_searchtext = Sökbegrepp yform_searchfields = Fälten som ska sökas igenom @@ -142,6 +147,7 @@ yform_function_button = Handling yform_add = tillfoga yform_add_apply = ta över yform_edit = redigera +yform_clone = klon yform_view = visa yform_save = spara yform_save_apply = ta över @@ -161,6 +167,8 @@ yform_dataset_delete = Radera dataresultatet yform_dataset_deleted = Dataresultatet raderades yform_dataset_delete_confirm = Radera dataresultatet? +yform_dataset_edit_confirm = Vill du verkligen redigera alla valda poster? + yform_data = Datasats yform_datas = Dataset yform_manager_search = Sök @@ -225,8 +233,10 @@ yform_manager_import_error_duplicatefielddefinition = Det finns fält som är id # history yform_history = Historik +yform_history_title = Historik för yform_history_dataset_id = Datasats-ID yform_history_dataset = Datasats +yform_history_dataset_current = Aktuell version yform_history_action = Aktion yform_history_action_create = skapat yform_history_action_update = redigerat @@ -235,6 +245,7 @@ yform_history_user = Användare yform_history_timestamp = Tidstämpel yform_history_view = visa yform_history_restore = återgå +yform_history_restore_confirm = Vill du verkligen återställa till den här versionen? yform_history_restore_this = återställ datasatsen till den här versionen yform_history_restore_success = Data återställdes yform_history_restore_error = Data kunde inte återställas @@ -243,6 +254,20 @@ yform_history_delete_all = totalt yform_history_delete_older_3_months = äldre än 3 månader yform_history_delete_confirm = Vill du verkligen radera historiken? yform_history_delete_success = Poster från historiken raderades. +yform_history_revision = Revision/Förändring +yform_history_dataset_missing = Inget (giltigt) datapost-ID har överförts! En jämförelse med en vald post i historiken är därför inte möjlig. +yform_history_diff_headline_changed = Ändrade Värden +yform_history_diff_headline_unchanged = Oförändrade Värden +yform_history_diff_headline_added = Tillagda Fält +yform_history_diff_headline_removed = Borttagna Fält +yform_history_diff_headline_prev_changed = Ändringar Jämfört med Föregående Version +yform_history_diff_to_current = Förändringar (till aktuell) +yform_history_diff_to_previous = Förändringar (till föregående) +yform_history_diff_added = Tillagd +yform_history_diff_removed = Borttagen +yform_history_diff_not_yet_existing = Ännu Inte Existerande +yform_history_diff_no_longer_existing = Inte Längre Existerande +yform_history_is_current_dataset = aktuella versionen av datasatsen yform_manager_show_form_notation = Formularkod @@ -291,3 +316,5 @@ yform_manager_table_nopermission = Ingen behörighet till den här tabellen yform_manager_tables_edit = Tabeller med redigeringsrättigheter yform_manager_tables_view = Tabeller med påsiktsrättigheter + +yform_structure_article_could_not_be_deleted = Artikeln kunde inte raderas eftersom den används av följande poster: diff --git a/plugins/manager/lib/yform/manager/dataset.php b/plugins/manager/lib/yform/manager/dataset.php index aca1e623..c28d21ab 100644 --- a/plugins/manager/lib/yform/manager/dataset.php +++ b/plugins/manager/lib/yform/manager/dataset.php @@ -743,7 +743,7 @@ private static function tableToModel(string $table): string /** * @internal */ - protected static function modelToTable(): string + final protected static function modelToTable(): string { $class = static::class; diff --git a/plugins/manager/lib/yform_history_helper.php b/plugins/manager/lib/yform_history_helper.php new file mode 100644 index 00000000..2e5d9471 --- /dev/null +++ b/plugins/manager/lib/yform_history_helper.php @@ -0,0 +1,182 @@ + field type icons */ + private const FIELD_TYPE_ICONS = [ + 'question' => 'question', + + 'checkbox' => 'square-check', + 'select' => 'list-check', + 'choice' => 'list-check', + 'choice_radio' => 'circle-dot', + 'choice_checkbox' => 'square-check', + 'date' => 'calendar', + 'datetime' => 'calendar-day', + 'datestamp' => 'calendar-day', + 'be_link' => 'link', + 'custom_link' => 'link', + 'be_media' => 'photo-film', + 'be_manager_relation' => 'database', + 'be_table' => 'table', + 'be_user' => 'user', + 'integer' => '1', + 'number' => '1', + 'ip' => 'network-wired', + 'generate_key' => 'key', + 'php' => 'code', + 'prio' => 'arrow-up-1-9', + 'signature' => 'signature', + 'submit' => 'fire', + 'time' => 'clock', + 'upload' => 'upload', + 'uuid' => 'key', + 'email' => 'at', + ]; + + /** @var string field type icons font width class */ + private const FIELD_TYPE_ICON_WEIGHT_CLASS = 'far'; + + /** + * detect diffs in 2 strings. + * @return array + * @author Peter Schulze | p.schulze[at]bitshifters.de + * @created 17.04.2024 + * @copyright https://github.com/paulgb/simplediff | Paul Butler (paulgb) + * @api + */ + public static function diffStrings($old, $new): array + { + $matrix = []; + $maxlen = $omax = $nmax = 0; + + foreach ($old as $oindex => $ovalue) { + $nkeys = array_keys($new, $ovalue, true); + + foreach ($nkeys as $nindex) { + $matrix[$oindex][$nindex] = + isset($matrix[$oindex - 1][$nindex - 1]) ? + $matrix[$oindex - 1][$nindex - 1] + 1 : + 1 + ; + + if ($matrix[$oindex][$nindex] > $maxlen) { + $maxlen = $matrix[$oindex][$nindex]; + $omax = $oindex + 1 - $maxlen; + $nmax = $nindex + 1 - $maxlen; + } + } + } + + if (0 === $maxlen) { + return [['d' => $old, 'i' => $new]]; + } + + return array_merge( + self::diffStrings(array_slice($old, 0, $omax), array_slice($new, 0, $nmax)), + array_slice($new, $nmax, $maxlen), + self::diffStrings(array_slice($old, $omax + $maxlen), array_slice($new, $nmax + $maxlen)), + ); + } + + /** + * detect diffs in 2 strings and return as html. + * @author Peter Schulze | p.schulze[at]bitshifters.de + * @created 17.04.2024 + * @copyright https://github.com/paulgb/simplediff | Paul Butler (paulgb) + * @api + */ + public static function diffStringsToHtml($old, $new): string + { + $ret = ''; + $diff = self::diffStrings(preg_split('/[\\s]+/', $old), preg_split('/[\\s]+/', $new)); + + foreach ($diff as $k) { + if (is_array($k)) { + $ret .= + (isset($k['d']) && is_array($k['d']) && count($k['d']) > 0 ? '' . implode(' ', $k['d']) . ' ' : '') . + (isset($k['i']) && is_array($k['i']) && count($k['i']) > 0 ? '' . implode(' ', $k['i']) . ' ' : '') + ; + } else { + $ret .= $k . ' '; + } + } + + return trim($ret); + } + + /** + * get icon for yform field type. + * @param bool $outputHtml print + * @author Peter Schulze | p.schulze[at]bitshifters.de + * @created 22.04.2024 + */ + public static function getFieldTypeIcon(rex_yform_manager_field $field, bool $addPrefix = true, bool $outputHtml = true, bool $addTooltip = true, string $tooltipPlacement = 'top'): string + { + $icon = self::FIELD_TYPE_ICONS[$field->getTypeName()] ?? 'default'; + $tag = isset(self::FIELD_TYPE_ICONS[$field->getTypeName()]) ? 'i' : 'span'; + + switch ($field->getTypeName()) { + case 'choice': + $expanded = (bool) (int) $field->getElement('expanded'); + $multiple = (bool) (int) $field->getElement('multiple'); + + if ($expanded && $multiple) { + $icon = self::FIELD_TYPE_ICONS['choice_checkbox']; + } elseif ($expanded) { + $icon = self::FIELD_TYPE_ICONS['choice_radio']; + } + break; + } + + return ($outputHtml ? '<' . + $tag . + ($addTooltip ? ' data-toggle="tooltip" data-placement="' . $tooltipPlacement . '" title="' . rex_i18n::msg('yform_manager_type_name') . ': ' . $field->getTypeName() . '"' : '') . + ' class="' : '' + ) . + ('default' !== $icon ? self::FIELD_TYPE_ICON_WEIGHT_CLASS . ' ' : '') . ($addPrefix ? 'rex-icon ' : '') . 'fa-' . $icon . + ($outputHtml ? '">' : ''); + } + + /** + * get field value. + * @author Peter Schulze | p.schulze[at]bitshifters.de + * @created 22.04.2024 + */ + public static function getFieldValue(rex_yform_manager_field $field, rex_yform_manager_dataset $dataset, rex_yform_manager_table $table): string + { + $class = 'rex_yform_value_' . $field->getTypeName(); + $currentValue = ($dataset->hasValue($field->getName()) ? $dataset->getValue($field->getName()) : '-'); + + if ( + is_callable([$class, 'getListValue']) && + !in_array($field->getTypeName(), ['text', 'textarea'], true) + ) { + // get (formatted) value for current entry + if ($dataset->hasValue($field->getName())) { + $currentValue = $class::getListValue([ + 'value' => $currentValue, + 'subject' => $currentValue, + 'field' => $field->getName(), + 'params' => [ + 'field' => $field->toArray(), + 'fields' => $table->getFields(), + ], + ]); + } else { + $currentValue = '-'; + } + } else { + $currentValue = rex_escape($currentValue); + } + + return $currentValue; + } +} diff --git a/plugins/manager/pages/data_history.php b/plugins/manager/pages/data_history.php index a4141360..03e04a7c 100644 --- a/plugins/manager/pages/data_history.php +++ b/plugins/manager/pages/data_history.php @@ -3,113 +3,71 @@ /** @var rex_yform_manager $this */ $subfunc = rex_request('subfunc', 'string'); -$datasetId = rex_request('data_id', 'int', null); +$datasetId = rex_request('data_id', 'int', 0); $filterDataset = rex_request('filter_dataset', 'bool'); $historyId = rex_request('history_id', 'int'); $_csrf_key ??= ''; -$historySearchId = rex_request('historySearchId', 'int', null); -$historySearchDate = rex_request('historySearchDate', 'string', null); -$historySearchUser = rex_request('historySearchUser', 'string', null); -$historySearchAction = rex_request('historySearchAction', 'string', null); +$historySearchId = rex_request('historySearchId', 'int', 0); +$historySearchDate = trim(rex_request('historySearchDate', 'string', '')); +$historySearchUser = trim(rex_request('historySearchUser', 'string', '')); +$historySearchAction = trim(rex_request('historySearchAction', 'string', '')); + +$table = $this->table; $dataset = null; -if ($datasetId) { - $dataset = rex_yform_manager_dataset::getRaw($datasetId, $this->table->getTableName()); +$filterWhere = ''; + +if ($datasetId > 0) { + $dataset = rex_yform_manager_dataset::getRaw($datasetId, $table->getTableName()); + + if ($filterDataset) { + // echo rex_view::info('' . rex_i18n::msg('yform_history_dataset_id') . ': ' . $datasetId); + $filterWhere = ' AND dataset_id = ' . $datasetId; + } } else { $filterDataset = false; } -$filterWhere = ''; -if ($filterDataset) { - echo rex_view::info('' . rex_i18n::msg('yform_history_dataset_id') . ': ' . $datasetId); - $filterWhere = ' AND dataset_id = ' . $datasetId; -} +// detailed dataset history +$isDatasetHistory = null !== $dataset && $dataset->exists() && $datasetId > 0; -if ($historySearchId) { +if ($historySearchId > 0 && !$isDatasetHistory) { $filterWhere .= ' AND dataset_id = ' . $historySearchId; } -if ($historySearchDate) { +if ('' !== $historySearchDate) { $historyDateObject = DateTime::createFromFormat('Y-m-d', $historySearchDate); + if (!$historyDateObject) { $historyDateObject = new DateTime(); } + $historyDateObject->modify('+1 day'); - $filterWhere .= ' AND timestamp <= ' . rex_sql::factory()->escape($historyDateObject->format('Y-m-d')); + $filterWhere .= ' AND timestamp < ' . rex_sql::factory()->escape($historyDateObject->format('Y-m-d')); } -if ($historySearchUser) { +if ('' !== $historySearchUser) { $filterWhere .= ' AND user =' . rex_sql::factory()->escape($historySearchUser); } -if ($historySearchAction) { +if ('' !== $historySearchAction) { $filterWhere .= ' AND action =' . rex_sql::factory()->escape($historySearchAction); } -if ('view' === $subfunc && $dataset && $historyId) { - $sql = rex_sql::factory(); - $timestamp = (string) $sql->setQuery(sprintf('SELECT `timestamp` FROM %s WHERE id = %d', rex::getTable('yform_history'), $historyId))->getValue('timestamp'); - - $data = $sql->getArray(sprintf('SELECT * FROM %s WHERE history_id = %d', rex::getTable('yform_history_field'), $historyId)); - $data = array_column($data, 'value', 'field'); - - $rows = ''; - - foreach ($this->table->getValueFields() as $field) { - if (!array_key_exists($field->getName(), $data)) { - continue; - } - - $value = $data[$field->getName()]; - $class = 'rex_yform_value_' . $field->getTypeName(); - if (method_exists($class, 'getListValue')) { - $value = $class::getListValue([ - 'value' => $value, - 'subject' => $value, - 'field' => $field->getName(), - 'params' => [ - 'field' => $field->toArray(), - 'fields' => $this->table->getFields(), - ], - ]); - } else { - $value = rex_escape($value); - } - - $rows .= ' - - ' . $field->getLabel() . ' - ' . $value . ' - '; - } - - $content = ' - - - - '; +if ('view' === $subfunc && $isDatasetHistory) { + $historyDiff = new rex_fragment(); + $historyDiff->setVar('history_id', $historyId); + $historyDiff->setVar('dataset_id', $datasetId); + $historyDiff->setVar('current_dataset', $dataset); + $historyDiff->setVar('table', $table, false); + $historyDiff->setVar('csrf_key', $_csrf_key); - rex_response::sendContent($content); + rex_response::sendContent($historyDiff->parse('yform/manager/history.diff.php')); exit; } -if ('restore' === $subfunc && $dataset && $historyId) { +if ('restore' === $subfunc && $isDatasetHistory) { if ($dataset->restoreSnapshot($historyId)) { echo rex_view::success(rex_i18n::msg('yform_history_restore_success')); } else { @@ -125,6 +83,7 @@ if (rex::getUser()->isAdmin() && in_array($subfunc, ['delete_old', 'delete_all'], true)) { $where = $filterWhere; + if ('delete_old' === $subfunc) { $where = ' AND h.`timestamp` < DATE_SUB(NOW(), INTERVAL 3 MONTH)'; } @@ -137,7 +96,7 @@ LEFT JOIN %s hf ON hf.history_id = h.id WHERE h.table_name = ? %s ', rex::getTable('yform_history'), rex::getTable('yform_history_field'), $where), - [$this->table->getTableName()], + [$table->getTableName()], ); echo rex_view::success(rex_i18n::msg('yform_history_delete_success')); @@ -145,56 +104,64 @@ $sql = rex_sql::factory(); -$listQuery = 'SELECT - h.id as hid, dataset_id, +$listQuery = ' + SELECT + h.id as hid, + dataset_id, id as title, `action`, `user`, `timestamp` FROM ' . rex::getTable('yform_history') . ' h WHERE - `table_name` = ' . $sql->escape($this->table->getTableName()) . + `table_name` = ' . $sql->escape($table->getTableName()) . $filterWhere; -$userQuery = 'SELECT +$userQuery = ' + SELECT distinct `user` FROM ' . rex::getTable('yform_history') . ' h WHERE - `table_name` = ' . $sql->escape($this->table->getTableName()); + `table_name` = ' . $sql->escape($table->getTableName()); $list = rex_list::factory($listQuery, defaultSort: [ - 'hid' => 'asc', 'timestamp' => 'desc', + 'hid' => 'asc', ]); +$list->addFormAttribute('class', 'history-list'); $users = $sql->getArray($userQuery); $users = array_combine(array_column($users, 'user'), array_column($users, 'user')); -$list->addParam('table_name', $this->table->getTableName()); +$list->addParam('table_name', $table->getTableName()); $list->addParam('func', 'history'); $list->addParam('_csrf_token', rex_csrf_token::factory($_csrf_key)->getValue()); if ($filterDataset) { $list->addParam('filter_dataset', 1); - $list->addParam('data_id', $datasetId); + + if (null !== $dataset && $dataset->exists() && $datasetId > 0) { + $list->addParam('data_id', $datasetId); + } } -if ($historySearchId) { +if ($historySearchId > 0) { $list->addParam('historySearchId', $historySearchId); } -if ($historySearchDate) { +if ('' !== $historySearchDate) { $list->addParam('historySearchDate', $historySearchDate); } -if ($historySearchUser) { +if ('' !== $historySearchUser) { $list->addParam('historySearchUser', $historySearchUser); } -if ($historySearchAction) { +if ('' !== $historySearchAction) { $list->addParam('historySearchAction', $historySearchAction); } $list->removeColumn('id'); +$list->setColumnLabel('hid', rex_i18n::msg('yform_id')); $list->setColumnLabel('dataset_id', rex_i18n::msg('yform_history_dataset_id')); $list->setColumnLabel('title', rex_i18n::msg('yform_history_dataset')); $list->setColumnFormat('title', 'custom', static function (array $params) { @@ -229,13 +196,198 @@ return (new DateTime($params['subject']))->format('d.m.Y H:i:s'); }); -$list->addColumn('view', ' ' . rex_i18n::msg('yform_history_view'), -1, ['', '###VALUE###']); -$list->setColumnParams('view', ['subfunc' => 'view', 'data_id' => '###dataset_id###', 'history_id' => '###hid###']); -$list->addLinkAttribute('view', 'data-toggle', 'modal'); -$list->addLinkAttribute('view', 'data-target', '#rex-yform-history-modal'); +$viewColumnBody = ' ' . rex_i18n::msg('yform_history_view'); +$restoreColumnBody = ' ' . rex_i18n::msg('yform_history_restore'); +$actionsCell = '###VALUE###'; +$normalCell = '###VALUE###'; -$list->addColumn('restore', ' ' . rex_i18n::msg('yform_history_restore'), -1, ['', '###VALUE###']); +if ($isDatasetHistory) { + // dataset column already in header, so not necessary to have it in the list + $list->removeColumn('dataset_id'); + $list->addColumn('view', $viewColumnBody, -1, ['', $actionsCell]); + + $list->setColumnParams('view', ['subfunc' => 'view', 'data_id' => '###dataset_id###', 'history_id' => '###hid###']); + $list->addLinkAttribute('view', 'data-toggle', 'modal'); + $list->addLinkAttribute('view', 'data-target', '#rex-yform-history-modal'); +} + +$list->addColumn('restore', $restoreColumnBody, -1, ['', $actionsCell]); $list->setColumnParams('restore', ['subfunc' => 'restore', 'data_id' => '###dataset_id###', 'history_id' => '###hid###'] + rex_csrf_token::factory($_csrf_key)->getUrlParams()); +$list->addLinkAttribute('restore', 'onclick', 'return confirm(\'' . rex_i18n::msg('yform_history_restore_confirm') . '\');'); + +$historyDatasets = []; +$sql = rex_sql::factory(); + +// when showing history of specific dataset +if ($isDatasetHistory) { + // revision / number + $revision = 'revision'; + $list->addColumn($revision, '', 2, [ + '' . rex_i18n::msg('yform_history_revision') . '', + '###VALUE###', + ]); + + rex::setProperty('YFORM_HISTORY_REVISION', 0); + + $list->setColumnFormat( + $revision, + 'custom', + static function ($a) use (&$rev, &$historyDatasets, &$table, &$sql) { + // early column ... store all values for current revision + $rev = rex::getProperty('YFORM_HISTORY_REVISION', 0); + + $historyDatasets[$rev] = [ + 'values' => [], + 'added' => [], + 'added_current' => [], + 'removed' => [], + 'removed_current' => [], + ]; + + $data = $sql->getArray(sprintf('SELECT * FROM %s WHERE history_id = :id', rex::getTable('yform_history_field')), [':id' => $a['list']->getValue('hid')]); + $data = array_column($data, 'value', 'field'); + + foreach ($table->getValueFields() as $field) { + $class = 'rex_yform_value_' . $field->getTypeName(); + + if (!array_key_exists($field->getName(), $data)) { + if (method_exists($class, 'getListValue')) { + $historyDatasets[$rev]['added'][$field->getName()] = $historyDatasets[$rev]['added_current'][$field->getName()] = true; + } + + continue; + } + + // set data + $historyDatasets[$rev]['values'][$field->getName()] = $data[$field->getName()]; + unset($data[$field->getName()]); + } + + // check for deleted fields in historic data + foreach ($data as $field => $value) { + $historyDatasets[$rev]['removed_current'][$field] = $value; + } + + // compare with prev + if (isset($historyDatasets[$rev - 1])) { + $prev = &$historyDatasets[$rev - 1]; + + // clean up added + foreach ($historyDatasets[$rev]['added'] as $field => $true) { + if (isset($prev['added'][$field])) { + unset($prev['added'][$field]); + } + } + + // handle removed + foreach ($prev['removed_current'] as $field => $value) { + if (!isset($historyDatasets[$rev]['removed_current'][$field])) { + $historyDatasets[$rev]['removed'][$field] = $value; + } + } + } + + rex::setProperty('YFORM_HISTORY_REVISION', $rev + 1); + // dump($historyDatasets); + return $rev; + }, + ); + + // changes compared to current dataset + $changesCurrent = 'changes_to_current'; + $list->addColumn($changesCurrent, '', 3, [ + '' . rex_i18n::msg('yform_history_diff_to_current') . '', + '###VALUE###', + ]); + + $viewColumnLayout = $list->getColumnLayout('view'); + + $list->setColumnFormat( + $changesCurrent, + 'custom', + static function ($a) use (&$dataset, $table, &$historyDatasets, $actionsCell, $normalCell, $changesCurrent) { + $rev = rex::getProperty('YFORM_HISTORY_REVISION', 0) - 1; + + $changes = 0; + $added = count($historyDatasets[$rev]['added_current']); + $removed = count($historyDatasets[$rev]['removed_current']); + + $historyDataset = &$historyDatasets[$rev]['values']; + + foreach ($table->getValueFields() as $field) { + if (!array_key_exists($field->getName(), $historyDataset)) { + continue; + } + + $historyValue = $historyDataset[$field->getName()]; + $currentValue = ($dataset->hasValue($field->getName()) ? $dataset->getValue($field->getName()) : '-'); + + if ('' . $historyValue !== '' . $currentValue) { + ++$changes; + } + } + + // handle actions column + if (0 === $changes) { + $a['list']->setColumnLayout($changesCurrent, ['', '###VALUE###']); + $a['list']->setColumnLayout('view', ['', '' . rex_i18n::msg('yform_history_is_current_dataset') . '']); + $a['list']->setColumnLayout('restore', ['', '']); + } else { + $a['list']->setColumnLayout($changesCurrent, ['', $normalCell]); + $a['list']->setColumnLayout('view', ['', $actionsCell]); + $a['list']->setColumnLayout('restore', ['', $actionsCell]); + } + + return $changes . + ($added > 0 || $removed > 0 ? + ' (' . ($added > 0 ? '+' . $added . ' ' . rex_i18n::msg('yform_history_diff_added') : '') . + ($removed > 0 ? ($added > 0 ? ', ' : '') . '+' . $removed . ' ' . rex_i18n::msg('yform_history_diff_removed') : '') + . ')' : '' + ); + }, + ); + + // changes compared to previous dataset + $changesPrev = 'changes_to_prev'; + $list->addColumn($changesPrev, '', 4, [ + '' . rex_i18n::msg('yform_history_diff_to_previous') . '', + '###VALUE###', + ]); + + $sql = rex_sql::factory(); + + $list->setColumnFormat( + $changesPrev, + 'custom', + static function ($a) use (&$historyDatasets) { + $rev = rex::getProperty('YFORM_HISTORY_REVISION', 0) - 1; + + $changes = 0; + $added = (isset($historyDatasets[$rev - 1]) ? count($historyDatasets[$rev - 1]['added']) : 0); + $removed = count($historyDatasets[$rev]['removed']); + + $historyDataset = &$historyDatasets[$rev]['values']; + + if (isset($historyDatasets[$rev - 1])) { + $prevHistoryDataset = $historyDatasets[$rev - 1]['values']; + + foreach ($historyDataset as $field => $value) { + if ('' . $historyDataset[$field] !== '' . $prevHistoryDataset[$field]) { + ++$changes; + // dump($rev.": ".$historyDataset[$field]." - ".$prevHistoryDataset[$field]." - ".$changes); + } + } + } + + return $changes . + ($added > 0 || $removed > 0 ? + ' (' . ($added > 0 ? '+' . $added . ' ' . rex_i18n::msg('yform_history_diff_added') : '') . + ($removed > 0 ? ($added > 0 ? ', ' : '') . '+' . $removed . ' ' . rex_i18n::msg('yform_history_diff_removed') : '') + . ')' : '' + ); + }, + ); +} $content = $list->get(); @@ -271,24 +423,27 @@ $historySearchForm->setObjectparams('csrf_protection', false); $historySearchForm->setHiddenField('_csrf_token', rex_csrf_token::factory($_csrf_key)->getValue()); -if (!$datasetId) { +if (!$isDatasetHistory) { $historySearchForm->setValueField('text', [ 'name' => 'historySearchId', - 'label' => 'id', + 'label' => rex_i18n::msg('yform_id'), ]); +} else { + $historySearchForm->setHiddenField('dataset_id', $datasetId); } $historySearchForm->setValueField('date', [ 'name' => 'historySearchDate', - 'label' => 'Date', + 'label' => rex_i18n::msg('yform_history_timestamp'), 'widget' => 'input:text', 'current_date' => true, 'notice' => rex_i18n::msg('yform_manager_history_date_notice'), 'attributes' => '{"data-yform-tools-datepicker":"YYYY-MM-DD"}', ]); + $historySearchForm->setValueField('choice', [ 'name' => 'historySearchAction', - 'label' => 'Action', + 'label' => rex_i18n::msg('yform_history_action'), 'choices' => [ '' => rex_i18n::msg('yform_manager_actions_all'), rex_yform_manager_dataset::ACTION_CREATE => rex_i18n::msg('yform_history_action_' . rex_yform_manager_dataset::ACTION_CREATE), @@ -299,7 +454,7 @@ $historySearchForm->setValueField('choice', [ 'name' => 'historySearchUser', - 'label' => 'User', + 'label' => rex_i18n::msg('yform_history_user'), 'choices' => array_merge( ['' => rex_i18n::msg('yform_manager_users_all')], $users, @@ -313,11 +468,21 @@ $searchForm = $fragment->parse('core/page/section.php'); $fragment = new rex_fragment(); -$fragment->setVar('title', rex_i18n::msg('yform_history')); + +if ($isDatasetHistory) { + $fragment->setVar('title', rex_i18n::msg('yform_history_title') . ' ' . rex_i18n::msg('yform_history_dataset_id') . ': ' . $datasetId . '', false); +} else { + $fragment->setVar('title', rex_i18n::msg('yform_history')); +} + $fragment->setVar('options', $options, false); $fragment->setVar('content', $content, false); $searchList = $fragment->parse('core/page/section.php'); +if ((null === $dataset || !$dataset->exists()) && $datasetId > 0) { + echo rex_view::warning(rex_i18n::msg('yform_history_dataset_missing')); +} + echo '
'; echo '
' . $searchForm . '
'; echo '
' . $searchList . '
';