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 = '
+
+
+';
+
+foreach ($diffs as $change => $diff) {
+ $content .= '
+
+
';
+
+ if('' !== $diff['rows']) {
+ $content .= '
+
+
+
+ ' . rex_i18n::msg('yform_tablefield') . ' |
+ ' . rex_i18n::msg('yform_history_dataset_current') . ' |
+ ' . date('d.m.Y H:i:s', strtotime($timestamp)) . ' |
+
+
+ ' . $diff['rows'] . '
+
';
+ }
+
+ $content .= '
';
+}
+
+$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 ? '">' . $tag . '>' : '');
+ }
+
+ /**
+ * 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 . '
';