Initial commit

This commit is contained in:
root 2026-02-23 16:11:35 +01:00
commit 53a0522417
65 changed files with 14605 additions and 0 deletions

14
.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
# Datenbankverbindung (enthält Passwörter)
ajax/db_connection.php
# Logdateien
*.log
# Backup-Dateien
*.bak
# Datenbankdumps
*.sql
# Versionierte Backup-Dateien
*.20*

477
Classifications.php Normal file
View File

@ -0,0 +1,477 @@
<?php
include "templates/Header.html";
?>
<!-- Hidden Fields -->
<input type="hidden" id="locale" value="de-DE">
<input type="hidden" id="ID" value="">
<input type="hidden" id="authType" value="Classification">
<!-- Main Content -->
<main class="main-content">
<div class="container">
<!-- Content Card -->
<div class="card">
<div class="d-flex justify-between align-center mb-20">
<h1 class="card-title" style="margin: 0;">🗂️ Klassifikationsverwaltung</h1>
<button id="add" class="btn btn-primary" onclick="newClassificationsShow()">
<i class="fas fa-plus"></i> Neue Klassifikation
</button>
</div>
<!-- Bootstrap Table -->
<table
id="table"
data-toolbar="#toolbar"
data-search="true"
data-query-params-type="limit"
data-show-refresh="false"
data-show-toggle="false"
data-show-fullscreen="false"
data-show-columns="false"
data-show-columns-toggle-all="false"
data-detail-view="false"
data-show-export="false"
data-click-to-select="false"
data-trim-on-search="false"
data-minimum-count-columns="2"
data-show-pagination-switch="false"
data-page-size="25"
data-pagination="true"
data-id-field="ID"
data-page-list="[10, 25, 50, 100, all]"
data-show-footer="false"
data-side-pagination="server"
data-server-sort="false"
data-url="/Thesaurus/ajax/getClassificationData.php"
data-response-handler="responseHandler">
</table>
</div>
<!-- Records Content -->
<div class="row">
<div class="records_content"></div>
</div>
</div>
</main>
<script>
var $table = $('#table')
var $remove = $('#remove')
var selections = []
function getIdSelections() {
return $.map($table.bootstrapTable('getSelections'), function (row) {
return row.ID
})
}
function responseHandler(res) {
$.each(res.rows, function (i, row) {
row.state = $.inArray(row.ID, selections) !== -1
})
return res
}
function detailFormatter(index, row) {
var html = []
$.each(row, function (key, value) {
if (key !== "ID" && key !== "state" && key != "Text" && key != "Descriptor") {
if (value !== null && value !== undefined && String(value).length > 0)
html.push('<p><b>' + key + ':</b> ' + value + '</p>')
}
})
return html.join('')
}
function copyFormatter(value, row, index) {
if (row.Descriptor === true || row.Descriptor === 'true' || row.Descriptor === 1) {
return '<button class="btn btn-sm btn-outline-secondary copy-btn" style="padding: 2px 6px; line-height: 1;" ' +
'onclick="event.stopPropagation(); copyTermToClipboard(\'' + row.Text.replace(/'/g, "\\'") + '\', this)" ' +
'title="Begriff kopieren">' +
'<i class="fas fa-copy" style="font-size: 0.75rem;"></i>' +
'</button>';
}
return '';
}
function operateFormatter(value, row, index) {
return [
'<div class="table-actions">',
'<a class="relations btn btn-sm btn-secondary" href="javascript:void(0)" title="Relationen anzeigen">',
'<i class="fas fa-table"></i>',
'</a>',
'<a class="modify btn btn-sm btn-primary" href="javascript:void(0)" title="Bearbeiten">',
'<i class="fas fa-edit"></i>',
'</a>',
'<a class="remove btn btn-sm btn-danger" href="javascript:void(0)" title="Löschen">',
'<i class="fas fa-trash"></i>',
'</a>',
'</div>'
].join('')
}
window.operateEvents = {
'click .relations': function (e, value, row, index) {
ShowRelationsModal(row.ID, 'Classification')
},
'click .modify': function (e, value, row, index) {
ModifyTerm([row.ID]),
$table.bootstrapTable('refresh')
},
'click .remove': function (e, value, row, index) {
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
return;
}
$table.bootstrapTable('remove', {
field: 'ID',
values: [row.ID]
})
DeleteTerm([row.ID])
}
}
function totalTextFormatter(data) {
return 'Total'
}
function totalNameFormatter(data) {
return data.length
}
function initTableClassification() {
$table.bootstrapTable('destroy').bootstrapTable({
locale: $('#locale').val(),
columns: [
[{
title: 'Klassifikation',
colspan: 4,
align: 'center'
}],
[{
field: 'copy',
title: '',
align: 'center',
width: 40,
clickToSelect: false,
formatter: copyFormatter
},
{
field: 'Notation',
title: 'Notation',
sortable: true,
align: 'left',
width: 120
},
{
field: 'Text',
title: 'Bezeichnung',
sortable: true,
align: 'left'
},
{
field: 'operate',
title: 'Funktionen',
align: 'center',
width: 160,
clickToSelect: false,
events: window.operateEvents,
formatter: operateFormatter
}]
]
})
$table.on('check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table',
function () {
$remove.prop('disabled', !$table.bootstrapTable('getSelections').length)
selections = getIdSelections()
}
)
$table.on('all.bs.table', function (e, name, args) {
console.log(name, args)
})
$remove.click(function () {
var ids = getIdSelections()
$table.bootstrapTable('remove', {
field: 'ID',
values: ids
})
$remove.prop('disabled', true)
})
}
function ReadDetails(RecordID) {
$('#ID').val(RecordID);
console.log('✅ RecordID gesetzt in #ID:', RecordID);
$('#search_relation').data('record-id', RecordID);
getData(1, 1, RecordID);
}
function getData(number, size, idvalue) {
var Type = document.getElementById("authType").value;
if (typeof idvalue == "undefined") {
var search = $("div.bootstrap-table > div.fixed-table-toolbar > div.search > input.form-control").val();
var id = 0
} else {
var id = idvalue
var search = ''
}
$.get('/Thesaurus/ajax/getClassificationData.php', {
id: id,
search: search,
offset: (number - 1) * size,
authType: Type,
limit: size
}, function (res) {
$table.bootstrapTable('load', res)
})
}
$(function() {
$table.on('page-change.bs.table', function (e, number, size, id) {
getData(number, size, id)
})
var options = $table.bootstrapTable('getOptions')
getData(options.pageNumber, options.pageSize)
})
$(function() {
initTableClassification()
$('#locale').change(initTableClassification)
})
</script>
<script>
// Globale searchRelation Funktion
window.searchRelation = function() {
const $input = $('#search_relation');
if ($input.length === 0) {
console.warn('⚠️ Element #search_relation nicht gefunden');
return;
}
// Entferne alte Event-Handler um Duplikate zu vermeiden
$input.off('input blur focus');
console.log('✓ Autocomplete initialisiert für:', $input.attr('id'));
let currentRequest = null;
$input.on('input', function() {
const query = $(this).val().trim();
console.log('🔍 Input erkannt:', query);
if (currentRequest) {
currentRequest.abort();
}
if (query.length < 2) {
console.log('⚠️ Zu kurz (minimum 2 Zeichen)');
$('.autocomplete-dropdown').remove();
return;
}
console.log('📡 Sende AJAX Anfrage...');
currentRequest = $.ajax({
url: '/Thesaurus/ajax/Autocomplete.php',
method: 'POST',
data: {
query: query,
authType: 'Classification'
},
dataType: 'json',
success: function(data) {
console.log('✅ Daten erhalten:', data.length, 'Einträge');
showAutocompleteResults(data, $input);
currentRequest = null;
},
error: function(xhr, status, error) {
if (status !== 'abort') {
console.error('❌ AJAX Fehler:', error);
}
currentRequest = null;
}
});
});
$input.on('blur', function() {
setTimeout(function() {
$('.autocomplete-dropdown').remove();
}, 200);
});
$input.on('focus', function() {
const query = $(this).val().trim();
if (query.length >= 2) {
$(this).trigger('input');
}
});
$(window).on('scroll resize', function() {
if ($('.autocomplete-dropdown').length > 0) {
const offset = $input.offset();
const inputHeight = $input.outerHeight();
const scrollTop = $(window).scrollTop();
const scrollLeft = $(window).scrollLeft();
$('.autocomplete-dropdown').css({
top: offset.top - scrollTop + inputHeight,
left: offset.left - scrollLeft
});
}
});
function showAutocompleteResults(data, $input) {
$('.autocomplete-dropdown').remove();
if (data.length === 0) {
console.log('⚠️ Keine Ergebnisse');
return;
}
const $dropdown = $('<div class="autocomplete-dropdown"></div>');
data.forEach(function(item) {
const match = item.match(/^(.+?)\s*\(ID:\s*(\d+)\)$/);
const label = match ? match[1].trim() : item;
const value = match ? match[2] : item;
const $item = $('<a class="dropdown-item" href="#"></a>')
.text(label)
.data('value', value)
.data('label', label)
.data('fulltext', item);
$item.on('click', function(e) {
e.preventDefault();
const selectedItem = {
label: $(this).data('label'),
value: $(this).data('value'),
fullText: $(this).data('fulltext')
};
console.log('✅ Ausgewählt:', selectedItem);
$input.val(selectedItem.label);
$input.data('selected-id', selectedItem.value);
$input.trigger('autocomplete:select', [selectedItem]);
$('.autocomplete-dropdown').remove();
// Speichere die Werte in Data-Attributen auf dem Button
const recordID = $('#ID').val();
console.log('🔍 recordID:', recordID);
console.log('🔍 selectedItem.value:', selectedItem.value);
$('#classification-anlegen').data('record-id', recordID);
$('#classification-anlegen').data('relation-id', selectedItem.value);
console.log('✅ Data-Attribute gesetzt auf #classification-anlegen');
});
$dropdown.append($item);
});
$('body').append($dropdown);
const offset = $input.offset();
const inputHeight = $input.outerHeight();
const inputWidth = $input.outerWidth();
const scrollTop = $(window).scrollTop();
const scrollLeft = $(window).scrollLeft();
$dropdown.css({
position: 'fixed',
top: offset.top - scrollTop + inputHeight,
left: offset.left - scrollLeft,
width: inputWidth,
'min-width': inputWidth,
'z-index': 9999
});
console.log('📋 Dropdown angezeigt mit', data.length, 'Einträgen');
}
};
// Automatische Initialisierung
$(document).ready(function() {
console.log('jQuery bereit');
$('.modal').on('shown.bs.modal', function () {
console.log('📂 Modal geöffnet');
if ($('#search_relation').length > 0) {
console.log('✅ search_relation gefunden, initialisiere...');
searchRelation();
}
});
if ($('#search_relation').length > 0) {
console.log('✅ search_relation beim Laden gefunden, initialisiere...');
searchRelation();
}
// Click-Handler für den "Hinzufügen" Button
$(document).on('click', '#classification-anlegen', function(event) {
event.preventDefault();
event.stopPropagation();
const recordID = $(this).data('record-id');
const relationID = $(this).data('relation-id');
console.log('🚀 Button geklickt - recordID:', recordID, '- relationID:', relationID);
if (recordID && relationID) {
CreateNewRelation(recordID, relationID, event);
} else {
console.warn('⚠️ recordID oder relationID fehlt!');
alert('Bitte zuerst einen Begriff aus der Autocomplete-Liste auswählen.');
}
});
});
</script>
<!-- Bootstrap Modals -->
<?php
$fp = fopen("templates/ClassificationsModal.tpl", "r");
if ($fp) {
echo (fread($fp, filesize("templates/ClassificationsModal.tpl")));
fclose($fp);
}
?>
<?php
$fp = fopen("templates/ClassificationDetailsModal.tpl", "r");
if ($fp) {
echo (fread($fp, filesize("templates/ClassificationDetailsModal.tpl")));
fclose($fp);
}
?>
<?php
$fp = fopen("templates/newClassification.tpl", "r");
if ($fp) {
echo (fread($fp, filesize("templates/newClassification.tpl")));
fclose($fp);
}
?>
<?php
$fp = fopen("templates/RelationsModal.tpl", "r");
if ($fp) {
echo (fread($fp, filesize("templates/RelationsModal.tpl")));
fclose($fp);
}
?>
<!-- Custom JS file -->
<script type="text/javascript" src="js/ClassificationScript.js"></script>
<?php
include "templates/Footer.html";
?>

476
Corporates.php Normal file
View File

@ -0,0 +1,476 @@
<?php
include "templates/Header.html";
?>
<!-- Hidden Fields -->
<input type="hidden" id="locale" value="de-DE">
<input type="hidden" id="ID" value="">
<input type="hidden" id="authType" value="Corporate">
<!-- Main Content -->
<main class="main-content">
<div class="container">
<!-- Content Card -->
<div class="card">
<div class="d-flex justify-between align-center mb-20">
<h1 class="card-title" style="margin: 0;">🏢 Körperschaftsverwaltung</h1>
<button id="add" class="btn btn-primary" onclick="newCorporatesShow()">
<i class="fas fa-plus"></i> Neue Körperschaft
</button>
</div>
<!-- Bootstrap Table -->
<table
id="table"
data-toolbar="#toolbar"
data-search="true"
data-query-params-type="limit"
data-show-refresh="false"
data-show-toggle="false"
data-show-fullscreen="false"
data-show-columns="false"
data-show-columns-toggle-all="false"
data-detail-view="false"
data-show-export="false"
data-click-to-select="false"
data-trim-on-search="false"
data-minimum-count-columns="2"
data-show-pagination-switch="false"
data-page-size="25"
data-pagination="true"
data-id-field="ID"
data-page-list="[10, 25, 50, 100, all]"
data-show-footer="false"
data-side-pagination="server"
data-server-sort="false"
data-url="/Thesaurus/ajax/getCorporateData.php"
data-response-handler="responseHandler">
</table>
</div>
<!-- Records Content -->
<div class="row">
<div class="records_content"></div>
</div>
</div>
</main>
<script>
var $table = $('#table')
var $remove = $('#remove')
var selections = []
function getIdSelections() {
return $.map($table.bootstrapTable('getSelections'), function (row) {
return row.ID
})
}
function responseHandler(res) {
$.each(res.rows, function (i, row) {
row.state = $.inArray(row.ID, selections) !== -1
})
return res
}
function detailFormatter(index, row) {
var html = []
$.each(row, function (key, value) {
if (key !== "ID" && key !== "state" && key != "Text" && key != "Descriptor") {
if (value !== null && value !== undefined && String(value).length > 0)
html.push('<p><b>' + key + ':</b> ' + value + '</p>')
}
})
return html.join('')
}
function copyFormatter(value, row, index) {
if (row.Descriptor === true || row.Descriptor === 'true' || row.Descriptor === 1) {
return '<button class="btn btn-sm btn-outline-secondary copy-btn" style="padding: 2px 6px; line-height: 1;" ' +
'onclick="event.stopPropagation(); copyTermToClipboard(\'' + row.Text.replace(/'/g, "\\'") + '\', this)" ' +
'title="Begriff kopieren">' +
'<i class="fas fa-copy" style="font-size: 0.75rem;"></i>' +
'</button>';
}
return '';
}
function operateFormatter(value, row, index) {
return [
'<div class="table-actions">',
'<a class="relations btn btn-sm btn-secondary" href="javascript:void(0)" title="Relationen anzeigen">',
'<i class="fas fa-table"></i>',
'</a>',
'<a class="details btn btn-sm btn-info" href="javascript:void(0)" title="Semantisches Netz">',
'<i class="fas fa-project-diagram"></i>',
'</a>',
'<a class="modify btn btn-sm btn-primary" href="javascript:void(0)" title="Bearbeiten">',
'<i class="fas fa-edit"></i>',
'</a>',
'<a class="remove btn btn-sm btn-danger" href="javascript:void(0)" title="Löschen">',
'<i class="fas fa-trash"></i>',
'</a>',
'</div>'
].join('')
}
window.operateEvents = {
'click .relations': function (e, value, row, index) {
ShowRelationsModal(row.ID, 'Corporate')
},
'click .details': function (e, value, row, index) {
ShowModalDetails(row.ID)
},
'click .modify': function (e, value, row, index) {
ModifyTerm([row.ID]),
$table.bootstrapTable('refresh')
},
'click .remove': function (e, value, row, index) {
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
return;
}
$table.bootstrapTable('remove', {
field: 'ID',
values: [row.ID]
})
DeleteTerm([row.ID])
}
}
function totalTextFormatter(data) {
return 'Total'
}
function totalNameFormatter(data) {
return data.length
}
function initTableCorporate() {
$table.bootstrapTable('destroy').bootstrapTable({
locale: $('#locale').val(),
columns: [
[{
title: 'Körperschaften',
colspan: 3,
align: 'center'
}],
[{
field: 'copy',
title: '',
align: 'center',
width: 40,
clickToSelect: false,
formatter: copyFormatter
},
{
field: 'Text',
title: 'Name',
sortable: true,
align: 'left'
},
{
field: 'operate',
title: 'Funktionen',
align: 'center',
width: 190,
clickToSelect: false,
events: window.operateEvents,
formatter: operateFormatter
}]
]
})
$table.on('check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table',
function () {
$remove.prop('disabled', !$table.bootstrapTable('getSelections').length)
selections = getIdSelections()
}
)
$table.on('all.bs.table', function (e, name, args) {
console.log(name, args)
})
$remove.click(function () {
var ids = getIdSelections()
$table.bootstrapTable('remove', {
field: 'ID',
values: ids
})
$remove.prop('disabled', true)
})
}
function ReadDetails(RecordID) {
$('#ID').val(RecordID);
console.log('✅ RecordID gesetzt in #ID:', RecordID);
$('#search_relation').data('record-id', RecordID);
getData(1, 1, RecordID);
}
function getData(number, size, idvalue) {
var Type = document.getElementById("authType").value;
if (typeof idvalue == "undefined") {
var search = $("div.bootstrap-table > div.fixed-table-toolbar > div.search > input.form-control").val();
var id = 0
} else {
var id = idvalue
var search = ''
}
$.get('/Thesaurus/ajax/getCorporateData.php', {
id: id,
search: search,
offset: (number - 1) * size,
authType: Type,
limit: size
}, function (res) {
$table.bootstrapTable('load', res)
})
}
$(function() {
$table.on('page-change.bs.table', function (e, number, size, id) {
getData(number, size, id)
})
var options = $table.bootstrapTable('getOptions')
getData(options.pageNumber, options.pageSize)
})
$(function() {
initTableCorporate()
$('#locale').change(initTableCorporate)
})
</script>
<script>
// Globale searchRelation Funktion
window.searchRelation = function() {
const $input = $('#search_relation');
if ($input.length === 0) {
console.warn('⚠️ Element #search_relation nicht gefunden');
return;
}
// Entferne alte Event-Handler um Duplikate zu vermeiden
$input.off('input blur focus');
console.log('✓ Autocomplete initialisiert für:', $input.attr('id'));
let currentRequest = null;
$input.on('input', function() {
const query = $(this).val().trim();
console.log('🔍 Input erkannt:', query);
if (currentRequest) {
currentRequest.abort();
}
if (query.length < 2) {
console.log('⚠️ Zu kurz (minimum 2 Zeichen)');
$('.autocomplete-dropdown').remove();
return;
}
console.log('📡 Sende AJAX Anfrage...');
currentRequest = $.ajax({
url: '/Thesaurus/ajax/Autocomplete.php',
method: 'POST',
data: {
query: query,
authType: 'Corporate'
},
dataType: 'json',
success: function(data) {
console.log('✅ Daten erhalten:', data.length, 'Einträge');
showAutocompleteResults(data, $input);
currentRequest = null;
},
error: function(xhr, status, error) {
if (status !== 'abort') {
console.error('❌ AJAX Fehler:', error);
}
currentRequest = null;
}
});
});
$input.on('blur', function() {
setTimeout(function() {
$('.autocomplete-dropdown').remove();
}, 200);
});
$input.on('focus', function() {
const query = $(this).val().trim();
if (query.length >= 2) {
$(this).trigger('input');
}
});
$(window).on('scroll resize', function() {
if ($('.autocomplete-dropdown').length > 0) {
const offset = $input.offset();
const inputHeight = $input.outerHeight();
const scrollTop = $(window).scrollTop();
const scrollLeft = $(window).scrollLeft();
$('.autocomplete-dropdown').css({
top: offset.top - scrollTop + inputHeight,
left: offset.left - scrollLeft
});
}
});
function showAutocompleteResults(data, $input) {
$('.autocomplete-dropdown').remove();
if (data.length === 0) {
console.log('⚠️ Keine Ergebnisse');
return;
}
const $dropdown = $('<div class="autocomplete-dropdown"></div>');
data.forEach(function(item) {
const match = item.match(/^(.+?)\s*\(ID:\s*(\d+)\)$/);
const label = match ? match[1].trim() : item;
const value = match ? match[2] : item;
const $item = $('<a class="dropdown-item" href="#"></a>')
.text(label)
.data('value', value)
.data('label', label)
.data('fulltext', item);
$item.on('click', function(e) {
e.preventDefault();
const selectedItem = {
label: $(this).data('label'),
value: $(this).data('value'),
fullText: $(this).data('fulltext')
};
console.log('✅ Ausgewählt:', selectedItem);
$input.val(selectedItem.label);
$input.data('selected-id', selectedItem.value);
$input.trigger('autocomplete:select', [selectedItem]);
$('.autocomplete-dropdown').remove();
// Speichere die Werte in Data-Attributen auf dem Button
const recordID = $('#ID').val();
console.log('🔍 recordID:', recordID);
console.log('🔍 selectedItem.value:', selectedItem.value);
$('#corporate-anlegen').data('record-id', recordID);
$('#corporate-anlegen').data('relation-id', selectedItem.value);
console.log('✅ Data-Attribute gesetzt auf #corporate-anlegen');
});
$dropdown.append($item);
});
$('body').append($dropdown);
const offset = $input.offset();
const inputHeight = $input.outerHeight();
const inputWidth = $input.outerWidth();
const scrollTop = $(window).scrollTop();
const scrollLeft = $(window).scrollLeft();
$dropdown.css({
position: 'fixed',
top: offset.top - scrollTop + inputHeight,
left: offset.left - scrollLeft,
width: inputWidth,
'min-width': inputWidth,
'z-index': 9999
});
console.log('📋 Dropdown angezeigt mit', data.length, 'Einträgen');
}
};
// Automatische Initialisierung
$(document).ready(function() {
console.log('jQuery bereit');
$('.modal').on('shown.bs.modal', function () {
console.log('📂 Modal geöffnet');
if ($('#search_relation').length > 0) {
console.log('✅ search_relation gefunden, initialisiere...');
searchRelation();
}
});
if ($('#search_relation').length > 0) {
console.log('✅ search_relation beim Laden gefunden, initialisiere...');
searchRelation();
}
// Click-Handler für den "Hinzufügen" Button
$(document).on('click', '#corporate-anlegen', function(event) {
event.preventDefault();
event.stopPropagation();
const recordID = $(this).data('record-id');
const relationID = $(this).data('relation-id');
console.log('🚀 Button geklickt - recordID:', recordID, '- relationID:', relationID);
if (recordID && relationID) {
CreateNewRelation(recordID, relationID, event);
} else {
console.warn('⚠️ recordID oder relationID fehlt!');
alert('Bitte zuerst einen Begriff aus der Autocomplete-Liste auswählen.');
}
});
});
</script>
<!-- Bootstrap Modals -->
<?php
$fp = fopen("templates/CorporatesModal.tpl", "r");
if ($fp) {
echo (fread($fp, filesize("templates/CorporatesModal.tpl")));
fclose($fp);
}
?>
<?php
$fp = fopen("templates/CorporateDetailsModal.tpl", "r");
if ($fp) {
echo (fread($fp, filesize("templates/CorporateDetailsModal.tpl")));
fclose($fp);
}
?>
<?php
$fp = fopen("templates/newCorporate.tpl", "r");
if ($fp) {
echo (fread($fp, filesize("templates/newCorporate.tpl")));
fclose($fp);
}
?>
<?php
$fp = fopen("templates/RelationsModal.tpl", "r");
if ($fp) {
echo (fread($fp, filesize("templates/RelationsModal.tpl")));
fclose($fp);
}
?>
<!-- Custom JS file -->
<script type="text/javascript" src="js/CorporateScript.js"></script>
<?php
include "templates/Footer.html";
?>

532
Persons.php Normal file
View File

@ -0,0 +1,532 @@
<?php
include "templates/Header.html";
?>
<!-- Hidden Fields -->
<input type="hidden" id="locale" value="de-DE">
<input type="hidden" id="ID" value="">
<input type="hidden" id="authType" value="Person">
<!-- Main Content -->
<main class="main-content">
<div class="container">
<!-- Content Card -->
<div class="card">
<div class="d-flex justify-between align-center mb-20">
<h1 class="card-title" style="margin: 0;">👥 Personenverwaltung</h1>
<button id="add" class="btn btn-primary" onclick="newPersonShow()">
<i class="fas fa-plus"></i> Neue Person
</button>
</div>
<!-- Bootstrap Table -->
<table
id="table"
data-toolbar="#toolbar"
data-search="true"
data-query-params-type="limit"
data-show-refresh="false"
data-show-toggle="false"
data-show-fullscreen="false"
data-show-columns="false"
data-show-columns-toggle-all="false"
data-detail-view="false"
data-show-export="false"
data-click-to-select="false"
data-trim-on-search="false"
data-minimum-count-columns="2"
data-show-pagination-switch="false"
data-page-size="25"
data-pagination="true"
data-id-field="ID"
data-page-list="[10, 25, 50, 100, all]"
data-show-footer="true"
data-side-pagination="server"
data-server-sort="false"
data-url="/Thesaurus/ajax/getPersonData.php"
data-response-handler="responseHandler">
</table>
</div>
<!-- Records Content -->
<div class="row">
<div class="records_content"></div>
</div>
</div>
</main>
<script>
var $table = $('#table')
var $remove = $('#remove')
var selections = []
function getIdSelections() {
return $.map($table.bootstrapTable('getSelections'), function (row) {
return row.ID
})
}
function responseHandler(res) {
$.each(res.rows, function (i, row) {
row.state = $.inArray(row.ID, selections) !== -1
})
return res
}
function detailFormatter(index, row) {
var html = []
$.each(row, function (key, value) {
if (key !== "ID" && key !== "state" && key != "Text" && key != "Descriptor") {
if (value !== null && value !== undefined && String(value).length > 0)
html.push('<p><b>' + key + ':</b> ' + value + '</p>')
}
})
return html.join('')
}
function copyFormatter(value, row, index) {
if (row.Descriptor === true || row.Descriptor === 'true' || row.Descriptor === 1) {
return '<button class="btn btn-sm btn-outline-secondary copy-btn" style="padding: 2px 6px; line-height: 1;" ' +
'onclick="event.stopPropagation(); copyTermToClipboard(\'' + row.Text.replace(/'/g, "\\'") + '\', this)" ' +
'title="Begriff kopieren">' +
'<i class="fas fa-copy" style="font-size: 0.75rem;"></i>' +
'</button>';
}
return '';
}
function dspaceCountFormatter(value, row, index) {
if (row.Descriptor !== true && row.Descriptor !== 'true' && row.Descriptor !== 1) {
return '<span class="text-muted" title="Kein Deskriptor"></span>';
}
return '<span class="dspace-count-cell" data-term="' + row.Text.replace(/"/g, '&quot;') + '" data-authtype="Person">' +
'<span class="spinner-border spinner-border-sm text-muted" style="width: 12px; height: 12px;" role="status"></span>' +
'</span>';
}
function operateFormatter(value, row, index) {
return [
'<div class="table-actions">',
'<a class="relations btn btn-sm btn-secondary" href="javascript:void(0)" title="Relationen anzeigen">',
'<i class="fas fa-table"></i>',
'</a>',
'<a class="details btn btn-sm btn-info" href="javascript:void(0)" title="Semantisches Netz">',
'<i class="fas fa-project-diagram"></i>',
'</a>',
'<a class="modify btn btn-sm btn-primary" href="javascript:void(0)" title="Bearbeiten">',
'<i class="fas fa-edit"></i>',
'</a>',
'<a class="remove btn btn-sm btn-danger" href="javascript:void(0)" title="Löschen">',
'<i class="fas fa-trash"></i>',
'</a>',
'</div>'
].join('')
}
window.operateEvents = {
'click .relations': function (e, value, row, index) {
ShowRelationsModal(row.ID, 'Person')
},
'click .details': function (e, value, row, index) {
ShowModalDetails(row.ID)
},
'click .modify': function (e, value, row, index) {
ModifyPerson([row.ID]),
$table.bootstrapTable('refresh')
},
'click .remove': function (e, value, row, index) {
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
return;
}
$table.bootstrapTable('remove', {
field: 'ID',
values: [row.ID]
})
DeleteTerm([row.ID])
}
}
function totalTextFormatter(data) {
return 'Total'
}
function totalNameFormatter(data) {
return data.length
}
function initTablePerson() {
$table.bootstrapTable('destroy').bootstrapTable({
locale: $('#locale').val(),
columns: [
[{
title: 'Personen',
colspan: 4,
align: 'center'
}],
[{
field: 'copy',
title: '',
align: 'center',
width: 40,
clickToSelect: false,
formatter: copyFormatter
},
{
field: 'Text',
title: 'Name',
sortable: true,
align: 'left'
},
{
field: 'DSpaceCount',
title: 'DSpace',
align: 'center',
width: 80,
clickToSelect: false,
formatter: dspaceCountFormatter
},
{
field: 'operate',
title: 'Funktionen',
align: 'center',
width: 190,
clickToSelect: false,
events: window.operateEvents,
formatter: operateFormatter
}]
]
})
$table.on('check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table',
function () {
$remove.prop('disabled', !$table.bootstrapTable('getSelections').length)
selections = getIdSelections()
}
)
$table.on('all.bs.table', function (e, name, args) {
console.log(name, args)
})
$remove.click(function () {
var ids = getIdSelections()
$table.bootstrapTable('remove', {
field: 'ID',
values: ids
})
$remove.prop('disabled', true)
})
}
function ReadDetails(RecordID) {
$('#ID').val(RecordID);
console.log('✅ RecordID gesetzt in #ID:', RecordID);
$('#search_relation').data('record-id', RecordID);
getData(1, 1, RecordID);
}
function getData(number, size, idvalue) {
var Type = document.getElementById("authType").value;
if (typeof idvalue == "undefined") {
var search = $("div.bootstrap-table > div.fixed-table-toolbar > div.search > input.form-control").val();
var id = 0
} else {
var id = idvalue
var search = ''
}
$.get('/Thesaurus/ajax/getPersonData.php', {
id: id,
search: search,
offset: (number - 1) * size,
authType: Type,
limit: size
}, function (res) {
$table.bootstrapTable('load', res)
})
}
$(function() {
$table.on('page-change.bs.table', function (e, number, size, id) {
getData(number, size, id)
})
var options = $table.bootstrapTable('getOptions')
getData(options.pageNumber, options.pageSize)
})
// DSpace-Counts asynchron laden nach dem Rendern der Tabelle
$(function() {
$table.on('post-body.bs.table', function () {
loadDSpaceCounts();
})
})
function loadDSpaceCounts() {
$('.dspace-count-cell').each(function() {
var $cell = $(this);
var term = $cell.data('term');
var authType = $cell.data('authtype');
if (!term || $cell.data('loaded')) return;
$cell.data('loaded', true);
$.ajax({
url: '/Thesaurus/ajax/getDSpaceCount.php',
type: 'GET',
data: { authType: authType, query: term },
dataType: 'json',
timeout: 15000,
success: function(data) {
var count = (data && typeof data.count !== 'undefined') ? data.count : 0;
if (count === -1) {
$cell.html('<span class="text-muted" title="DSpace nicht erreichbar"><i class="fas fa-exclamation-triangle" style="font-size: 0.75rem;"></i></span>');
} else if (count > 0) {
$cell.html('<span class="badge" style="background-color: #1a3a5c;" title="' + count + ' Datensätze in DSpace">' + count + '</span>');
} else {
$cell.html('<span class="badge bg-danger" title="Keine Datensätze in DSpace">0</span>');
}
},
error: function() {
$cell.html('<span class="text-muted" title="Fehler"><i class="fas fa-exclamation-triangle" style="font-size: 0.75rem;"></i></span>');
}
});
});
}
$(function() {
initTablePerson()
$('#locale').change(initTablePerson)
})
</script>
<script>
// Globale searchRelation Funktion
window.searchRelation = function() {
const $input = $('#search_relation');
if ($input.length === 0) {
console.warn('⚠️ Element #search_relation nicht gefunden');
return;
}
// Entferne alte Event-Handler um Duplikate zu vermeiden
$input.off('input blur focus');
console.log('✓ Autocomplete initialisiert für:', $input.attr('id'));
let currentRequest = null;
$input.on('input', function() {
const query = $(this).val().trim();
console.log('🔍 Input erkannt:', query);
if (currentRequest) {
currentRequest.abort();
}
if (query.length < 2) {
console.log('⚠️ Zu kurz (minimum 2 Zeichen)');
$('.autocomplete-dropdown').remove();
return;
}
console.log('📡 Sende AJAX Anfrage...');
currentRequest = $.ajax({
url: '/Thesaurus/ajax/Autocomplete.php',
method: 'POST',
data: {
query: query,
authType: 'Person'
},
dataType: 'json',
success: function(data) {
console.log('✅ Daten erhalten:', data.length, 'Einträge');
showAutocompleteResults(data, $input);
currentRequest = null;
},
error: function(xhr, status, error) {
if (status !== 'abort') {
console.error('❌ AJAX Fehler:', error);
}
currentRequest = null;
}
});
});
$input.on('blur', function() {
setTimeout(function() {
$('.autocomplete-dropdown').remove();
}, 200);
});
$input.on('focus', function() {
const query = $(this).val().trim();
if (query.length >= 2) {
$(this).trigger('input');
}
});
$(window).on('scroll resize', function() {
if ($('.autocomplete-dropdown').length > 0) {
const offset = $input.offset();
const inputHeight = $input.outerHeight();
const scrollTop = $(window).scrollTop();
const scrollLeft = $(window).scrollLeft();
$('.autocomplete-dropdown').css({
top: offset.top - scrollTop + inputHeight,
left: offset.left - scrollLeft
});
}
});
function showAutocompleteResults(data, $input) {
$('.autocomplete-dropdown').remove();
if (data.length === 0) {
console.log('⚠️ Keine Ergebnisse');
return;
}
const $dropdown = $('<div class="autocomplete-dropdown"></div>');
data.forEach(function(item) {
const match = item.match(/^(.+?)\s*\(ID:\s*(\d+)\)$/);
const label = match ? match[1].trim() : item;
const value = match ? match[2] : item;
const $item = $('<a class="dropdown-item" href="#"></a>')
.text(label)
.data('value', value)
.data('label', label)
.data('fulltext', item);
$item.on('click', function(e) {
e.preventDefault();
const selectedItem = {
label: $(this).data('label'),
value: $(this).data('value'),
fullText: $(this).data('fulltext')
};
console.log('✅ Ausgewählt:', selectedItem);
$input.val(selectedItem.label);
$input.data('selected-id', selectedItem.value);
$input.trigger('autocomplete:select', [selectedItem]);
$('.autocomplete-dropdown').remove();
// Speichere die Werte in Data-Attributen auf dem Button
const recordID = $('#ID').val();
console.log('🔍 recordID:', recordID);
console.log('🔍 selectedItem.value:', selectedItem.value);
$('#subject-anlegen').data('record-id', recordID);
$('#subject-anlegen').data('relation-id', selectedItem.value);
console.log('✅ Data-Attribute gesetzt auf #subject-anlegen');
});
$dropdown.append($item);
});
$('body').append($dropdown);
const offset = $input.offset();
const inputHeight = $input.outerHeight();
const inputWidth = $input.outerWidth();
const scrollTop = $(window).scrollTop();
const scrollLeft = $(window).scrollLeft();
$dropdown.css({
position: 'fixed',
top: offset.top - scrollTop + inputHeight,
left: offset.left - scrollLeft,
width: inputWidth,
'min-width': inputWidth,
'z-index': 9999
});
console.log('📋 Dropdown angezeigt mit', data.length, 'Einträgen');
}
};
// Automatische Initialisierung
$(document).ready(function() {
console.log('jQuery bereit');
$('.modal').on('shown.bs.modal', function () {
console.log('📂 Modal geöffnet');
if ($('#search_relation').length > 0) {
console.log('✅ search_relation gefunden, initialisiere...');
searchRelation();
}
});
if ($('#search_relation').length > 0) {
console.log('✅ search_relation beim Laden gefunden, initialisiere...');
searchRelation();
}
// Click-Handler für den "Hinzufügen" Button
$(document).on('click', '#subject-anlegen', function(event) {
event.preventDefault();
event.stopPropagation();
const recordID = $(this).data('record-id');
const relationID = $(this).data('relation-id');
console.log('🚀 Button geklickt - recordID:', recordID, '- relationID:', relationID);
if (recordID && relationID) {
CreateNewRelation(recordID, relationID, event);
} else {
console.warn('⚠️ recordID oder relationID fehlt!');
alert('Bitte zuerst einen Begriff aus der Autocomplete-Liste auswählen.');
}
});
});
</script>
<!-- Bootstrap Modals -->
<?php
$fp = fopen("templates/PersonsModal.tpl", "r");
if ($fp) {
echo (fread($fp, filesize("templates/PersonsModal.tpl")));
fclose($fp);
}
?>
<?php
$fp = fopen("templates/PersonDetailsModal.tpl", "r");
if ($fp) {
echo (fread($fp, filesize("templates/PersonDetailsModal.tpl")));
fclose($fp);
}
?>
<?php
$fp = fopen("templates/newPerson.tpl", "r");
if ($fp) {
echo (fread($fp, filesize("templates/newPerson.tpl")));
fclose($fp);
}
?>
<?php
$fp = fopen("templates/RelationsModal.tpl", "r");
if ($fp) {
echo (fread($fp, filesize("templates/RelationsModal.tpl")));
fclose($fp);
}
?>
<!-- Custom JS file -->
<script type="text/javascript" src="js/PersonScript.js"></script>
<?php
include "templates/Footer.html";
?>

476
Publishers.php Normal file
View File

@ -0,0 +1,476 @@
<?php
include "templates/Header.html";
?>
<!-- Hidden Fields -->
<input type="hidden" id="locale" value="de-DE">
<input type="hidden" id="ID" value="">
<input type="hidden" id="authType" value="Publisher">
<!-- Main Content -->
<main class="main-content">
<div class="container">
<!-- Content Card -->
<div class="card">
<div class="d-flex justify-between align-center mb-20">
<h1 class="card-title" style="margin: 0;">📚 Verlagsverwaltung</h1>
<button id="add" class="btn btn-primary" onclick="newPublishersShow()">
<i class="fas fa-plus"></i> Neuer Verlag
</button>
</div>
<!-- Bootstrap Table -->
<table
id="table"
data-toolbar="#toolbar"
data-search="true"
data-query-params-type="limit"
data-show-refresh="false"
data-show-toggle="false"
data-show-fullscreen="false"
data-show-columns="false"
data-show-columns-toggle-all="false"
data-detail-view="false"
data-show-export="false"
data-click-to-select="false"
data-trim-on-search="false"
data-minimum-count-columns="2"
data-show-pagination-switch="false"
data-page-size="25"
data-pagination="true"
data-id-field="ID"
data-page-list="[10, 25, 50, 100, all]"
data-show-footer="false"
data-side-pagination="server"
data-server-sort="false"
data-url="/Thesaurus/ajax/getPublisherData.php"
data-response-handler="responseHandler">
</table>
</div>
<!-- Records Content -->
<div class="row">
<div class="records_content"></div>
</div>
</div>
</main>
<script>
var $table = $('#table')
var $remove = $('#remove')
var selections = []
function getIdSelections() {
return $.map($table.bootstrapTable('getSelections'), function (row) {
return row.ID
})
}
function responseHandler(res) {
$.each(res.rows, function (i, row) {
row.state = $.inArray(row.ID, selections) !== -1
})
return res
}
function detailFormatter(index, row) {
var html = []
$.each(row, function (key, value) {
if (key !== "ID" && key !== "state" && key != "Text" && key != "Descriptor") {
if (value !== null && value !== undefined && String(value).length > 0)
html.push('<p><b>' + key + ':</b> ' + value + '</p>')
}
})
return html.join('')
}
function copyFormatter(value, row, index) {
if (row.Descriptor === true || row.Descriptor === 'true' || row.Descriptor === 1) {
return '<button class="btn btn-sm btn-outline-secondary copy-btn" style="padding: 2px 6px; line-height: 1;" ' +
'onclick="event.stopPropagation(); copyTermToClipboard(\'' + row.Text.replace(/'/g, "\\'") + '\', this)" ' +
'title="Begriff kopieren">' +
'<i class="fas fa-copy" style="font-size: 0.75rem;"></i>' +
'</button>';
}
return '';
}
function operateFormatter(value, row, index) {
return [
'<div class="table-actions">',
'<a class="relations btn btn-sm btn-secondary" href="javascript:void(0)" title="Relationen anzeigen">',
'<i class="fas fa-table"></i>',
'</a>',
'<a class="details btn btn-sm btn-info" href="javascript:void(0)" title="Semantisches Netz">',
'<i class="fas fa-project-diagram"></i>',
'</a>',
'<a class="modify btn btn-sm btn-primary" href="javascript:void(0)" title="Bearbeiten">',
'<i class="fas fa-edit"></i>',
'</a>',
'<a class="remove btn btn-sm btn-danger" href="javascript:void(0)" title="Löschen">',
'<i class="fas fa-trash"></i>',
'</a>',
'</div>'
].join('')
}
window.operateEvents = {
'click .relations': function (e, value, row, index) {
ShowRelationsModal(row.ID, 'Publisher')
},
'click .details': function (e, value, row, index) {
ShowModalDetails(row.ID)
},
'click .modify': function (e, value, row, index) {
ModifyTerm([row.ID]),
$table.bootstrapTable('refresh')
},
'click .remove': function (e, value, row, index) {
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
return;
}
$table.bootstrapTable('remove', {
field: 'ID',
values: [row.ID]
})
DeleteTerm([row.ID])
}
}
function totalTextFormatter(data) {
return 'Total'
}
function totalNameFormatter(data) {
return data.length
}
function initTablePublisher() {
$table.bootstrapTable('destroy').bootstrapTable({
locale: $('#locale').val(),
columns: [
[{
title: 'Verlage',
colspan: 3,
align: 'center'
}],
[{
field: 'copy',
title: '',
align: 'center',
width: 40,
clickToSelect: false,
formatter: copyFormatter
},
{
field: 'Text',
title: 'Name',
sortable: true,
align: 'left'
},
{
field: 'operate',
title: 'Funktionen',
align: 'center',
width: 190,
clickToSelect: false,
events: window.operateEvents,
formatter: operateFormatter
}]
]
})
$table.on('check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table',
function () {
$remove.prop('disabled', !$table.bootstrapTable('getSelections').length)
selections = getIdSelections()
}
)
$table.on('all.bs.table', function (e, name, args) {
console.log(name, args)
})
$remove.click(function () {
var ids = getIdSelections()
$table.bootstrapTable('remove', {
field: 'ID',
values: ids
})
$remove.prop('disabled', true)
})
}
function ReadDetails(RecordID) {
$('#ID').val(RecordID);
console.log('✅ RecordID gesetzt in #ID:', RecordID);
$('#search_relation').data('record-id', RecordID);
getData(1, 1, RecordID);
}
function getData(number, size, idvalue) {
var Type = document.getElementById("authType").value;
if (typeof idvalue == "undefined") {
var search = $("div.bootstrap-table > div.fixed-table-toolbar > div.search > input.form-control").val();
var id = 0
} else {
var id = idvalue
var search = ''
}
$.get('/Thesaurus/ajax/getPublisherData.php', {
id: id,
search: search,
offset: (number - 1) * size,
authType: Type,
limit: size
}, function (res) {
$table.bootstrapTable('load', res)
})
}
$(function() {
$table.on('page-change.bs.table', function (e, number, size, id) {
getData(number, size, id)
})
var options = $table.bootstrapTable('getOptions')
getData(options.pageNumber, options.pageSize)
})
$(function() {
initTablePublisher()
$('#locale').change(initTablePublisher)
})
</script>
<script>
// Globale searchRelation Funktion
window.searchRelation = function() {
const $input = $('#search_relation');
if ($input.length === 0) {
console.warn('⚠️ Element #search_relation nicht gefunden');
return;
}
// Entferne alte Event-Handler um Duplikate zu vermeiden
$input.off('input blur focus');
console.log('✓ Autocomplete initialisiert für:', $input.attr('id'));
let currentRequest = null;
$input.on('input', function() {
const query = $(this).val().trim();
console.log('🔍 Input erkannt:', query);
if (currentRequest) {
currentRequest.abort();
}
if (query.length < 2) {
console.log('⚠️ Zu kurz (minimum 2 Zeichen)');
$('.autocomplete-dropdown').remove();
return;
}
console.log('📡 Sende AJAX Anfrage...');
currentRequest = $.ajax({
url: '/Thesaurus/ajax/Autocomplete.php',
method: 'POST',
data: {
query: query,
authType: 'Publisher'
},
dataType: 'json',
success: function(data) {
console.log('✅ Daten erhalten:', data.length, 'Einträge');
showAutocompleteResults(data, $input);
currentRequest = null;
},
error: function(xhr, status, error) {
if (status !== 'abort') {
console.error('❌ AJAX Fehler:', error);
}
currentRequest = null;
}
});
});
$input.on('blur', function() {
setTimeout(function() {
$('.autocomplete-dropdown').remove();
}, 200);
});
$input.on('focus', function() {
const query = $(this).val().trim();
if (query.length >= 2) {
$(this).trigger('input');
}
});
$(window).on('scroll resize', function() {
if ($('.autocomplete-dropdown').length > 0) {
const offset = $input.offset();
const inputHeight = $input.outerHeight();
const scrollTop = $(window).scrollTop();
const scrollLeft = $(window).scrollLeft();
$('.autocomplete-dropdown').css({
top: offset.top - scrollTop + inputHeight,
left: offset.left - scrollLeft
});
}
});
function showAutocompleteResults(data, $input) {
$('.autocomplete-dropdown').remove();
if (data.length === 0) {
console.log('⚠️ Keine Ergebnisse');
return;
}
const $dropdown = $('<div class="autocomplete-dropdown"></div>');
data.forEach(function(item) {
const match = item.match(/^(.+?)\s*\(ID:\s*(\d+)\)$/);
const label = match ? match[1].trim() : item;
const value = match ? match[2] : item;
const $item = $('<a class="dropdown-item" href="#"></a>')
.text(label)
.data('value', value)
.data('label', label)
.data('fulltext', item);
$item.on('click', function(e) {
e.preventDefault();
const selectedItem = {
label: $(this).data('label'),
value: $(this).data('value'),
fullText: $(this).data('fulltext')
};
console.log('✅ Ausgewählt:', selectedItem);
$input.val(selectedItem.label);
$input.data('selected-id', selectedItem.value);
$input.trigger('autocomplete:select', [selectedItem]);
$('.autocomplete-dropdown').remove();
// Speichere die Werte in Data-Attributen auf dem Button
const recordID = $('#ID').val();
console.log('🔍 recordID:', recordID);
console.log('🔍 selectedItem.value:', selectedItem.value);
$('#publisher-anlegen').data('record-id', recordID);
$('#publisher-anlegen').data('relation-id', selectedItem.value);
console.log('✅ Data-Attribute gesetzt auf #publisher-anlegen');
});
$dropdown.append($item);
});
$('body').append($dropdown);
const offset = $input.offset();
const inputHeight = $input.outerHeight();
const inputWidth = $input.outerWidth();
const scrollTop = $(window).scrollTop();
const scrollLeft = $(window).scrollLeft();
$dropdown.css({
position: 'fixed',
top: offset.top - scrollTop + inputHeight,
left: offset.left - scrollLeft,
width: inputWidth,
'min-width': inputWidth,
'z-index': 9999
});
console.log('📋 Dropdown angezeigt mit', data.length, 'Einträgen');
}
};
// Automatische Initialisierung
$(document).ready(function() {
console.log('jQuery bereit');
$('.modal').on('shown.bs.modal', function () {
console.log('📂 Modal geöffnet');
if ($('#search_relation').length > 0) {
console.log('✅ search_relation gefunden, initialisiere...');
searchRelation();
}
});
if ($('#search_relation').length > 0) {
console.log('✅ search_relation beim Laden gefunden, initialisiere...');
searchRelation();
}
// Click-Handler für den "Hinzufügen" Button
$(document).on('click', '#publisher-anlegen', function(event) {
event.preventDefault();
event.stopPropagation();
const recordID = $(this).data('record-id');
const relationID = $(this).data('relation-id');
console.log('🚀 Button geklickt - recordID:', recordID, '- relationID:', relationID);
if (recordID && relationID) {
CreateNewRelation(recordID, relationID, event);
} else {
console.warn('⚠️ recordID oder relationID fehlt!');
alert('Bitte zuerst einen Begriff aus der Autocomplete-Liste auswählen.');
}
});
});
</script>
<!-- Bootstrap Modals -->
<?php
$fp = fopen("templates/PublishersModal.tpl", "r");
if ($fp) {
echo (fread($fp, filesize("templates/PublishersModal.tpl")));
fclose($fp);
}
?>
<?php
$fp = fopen("templates/PublisherDetailsModal.tpl", "r");
if ($fp) {
echo (fread($fp, filesize("templates/PublisherDetailsModal.tpl")));
fclose($fp);
}
?>
<?php
$fp = fopen("templates/newPublisher.tpl", "r");
if ($fp) {
echo (fread($fp, filesize("templates/newPublisher.tpl")));
fclose($fp);
}
?>
<?php
$fp = fopen("templates/RelationsModal.tpl", "r");
if ($fp) {
echo (fread($fp, filesize("templates/RelationsModal.tpl")));
fclose($fp);
}
?>
<!-- Custom JS file -->
<script type="text/javascript" src="js/PublisherScript.js"></script>
<?php
include "templates/Footer.html";
?>

22
README.md Normal file
View File

@ -0,0 +1,22 @@
# Thesaurus
Webbasierte Thesaurus-Verwaltung für Schlagwörter, Personen, Körperschaften, Verlage und Klassifikationen.
## Voraussetzungen
- PHP 7.4 oder höher
- MySQL/MariaDB
- Apache Webserver
## Installation
1. Repository klonen
2. `ajax/db_connection.php.example` kopieren und umbenennen:
```bash
cp ajax/db_connection.php.example ajax/db_connection.php
```
3. Zugangsdaten in `ajax/db_connection.php` eintragen
4. Abhängigkeiten installieren:
```bash
bash setup-libs.sh
```

545
Subjects.php Normal file
View File

@ -0,0 +1,545 @@
<?php
include "templates/Header.html";
?>
<!-- Hidden Fields -->
<input type="hidden" id="locale" value="de-DE">
<input type="hidden" id="ID" value="">
<input type="hidden" id="authType" value="Subject">
<!-- Main Content -->
<main class="main-content">
<div class="container">
<!-- Content Card -->
<div class="card">
<div class="d-flex justify-between align-center mb-20">
<h1 class="card-title" style="margin: 0;">🏷️ Schlagwortverwaltung</h1>
<button id="add" class="btn btn-primary" onclick="newSubjectShow()">
<i class="fas fa-plus"></i> Neuer Deskriptor
</button>
</div>
<!-- Bootstrap Table -->
<table
id="table"
data-toolbar="#toolbar"
data-search="true"
data-query-params-type="limit"
data-show-refresh="false"
data-show-toggle="false"
data-show-fullscreen="false"
data-show-columns="false"
data-show-columns-toggle-all="false"
data-detail-view="false"
data-show-export="false"
data-click-to-select="false"
data-trim-on-search="false"
data-minimum-count-columns="2"
data-show-pagination-switch="false"
data-page-size="25"
data-pagination="true"
data-id-field="ID"
data-page-list="[10, 25, 50, 100, all]"
data-show-footer="true"
data-side-pagination="server"
data-server-sort="true"
data-url="/Thesaurus/ajax/getSubjectData.php"
data-response-handler="responseHandler">
</table>
</div>
<!-- Records Content -->
<div class="row">
<div class="records_content"></div>
</div>
</div>
</main>
<script>
var $table = $('#table')
var $remove = $('#remove')
var selections = []
function getIdSelections() {
return $.map($table.bootstrapTable('getSelections'), function (row) {
return row.ID
})
}
function responseHandler(res) {
$.each(res.rows, function (i, row) {
row.state = $.inArray(row.ID, selections) !== -1
})
return res
}
function detailFormatter(index, row) {
var html = []
$.each(row, function (key, value) {
if (key !== "ID" && key !== "state" && key != "Text" && key != "Descriptor") {
if (value !== null && value !== undefined && String(value).length > 0)
html.push('<p><b>' + key + ':</b> ' + value + '</p>')
}
})
return html.join('')
}
function copyFormatter(value, row, index) {
if (row.Descriptor === true || row.Descriptor === 'true' || row.Descriptor === 1) {
return '<button class="btn btn-sm btn-outline-secondary copy-btn" style="padding: 2px 6px; line-height: 1;" ' +
'onclick="event.stopPropagation(); copyTermToClipboard(\'' + row.Text.replace(/'/g, "\\'") + '\', this)" ' +
'title="Begriff kopieren">' +
'<i class="fas fa-copy" style="font-size: 0.75rem;"></i>' +
'</button>';
}
return '';
}
function dspaceCountFormatter(value, row, index) {
if (row.Descriptor !== true && row.Descriptor !== 'true' && row.Descriptor !== 1) {
return '<span class="text-muted" title="Kein Deskriptor"></span>';
}
return '<span class="dspace-count-cell" data-term="' + row.Text.replace(/"/g, '&quot;') + '" data-authtype="Subject">' +
'<span class="spinner-border spinner-border-sm text-muted" style="width: 12px; height: 12px;" role="status"></span>' +
'</span>';
}
function operateFormatter(value, row, index) {
return [
'<div class="table-actions">',
'<a class="relations btn btn-sm btn-secondary" href="javascript:void(0)" title="Relationen anzeigen">',
'<i class="fas fa-table"></i>',
'</a>',
'<a class="details btn btn-sm btn-info" href="javascript:void(0)" title="Semantisches Netz">',
'<i class="fas fa-project-diagram"></i>',
'</a>',
'<a class="modify btn btn-sm btn-primary" href="javascript:void(0)" title="Bearbeiten">',
'<i class="fas fa-edit"></i>',
'</a>',
'<a class="remove btn btn-sm btn-danger" href="javascript:void(0)" title="Löschen">',
'<i class="fas fa-trash"></i>',
'</a>',
'</div>'
].join('')
}
window.operateEvents = {
'click .relations': function (e, value, row, index) {
ShowRelationsModal(row.ID, 'Subject')
},
'click .details': function (e, value, row, index) {
ShowModalDetails(row.ID)
},
'click .modify': function (e, value, row, index) {
ModifyTerm([row.ID]),
$table.bootstrapTable('refresh')
},
'click .remove': function (e, value, row, index) {
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
return;
}
$table.bootstrapTable('remove', {
field: 'ID',
values: [row.ID]
})
DeleteTerm([row.ID])
}
}
function totalTextFormatter(data) {
return 'Total'
}
function totalNameFormatter(data) {
return data.length
}
function initTableSubject() {
$table.bootstrapTable('destroy').bootstrapTable({
locale: $('#locale').val(),
columns: [
[{
title: 'Schlagworte',
colspan: 4,
align: 'center'
}],
[{
field: 'copy',
title: '',
align: 'center',
width: 40,
clickToSelect: false,
formatter: copyFormatter
},
{
field: 'Text',
title: 'Begriff',
sortable: true,
align: 'left'
},
{
field: 'DSpaceCount',
title: 'DSpace',
align: 'center',
width: 80,
clickToSelect: false,
formatter: dspaceCountFormatter
},
{
field: 'operate',
title: 'Funktionen',
align: 'center',
width: 190,
clickToSelect: false,
events: window.operateEvents,
formatter: operateFormatter
}]
]
})
$table.on('check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table',
function () {
$remove.prop('disabled', !$table.bootstrapTable('getSelections').length)
selections = getIdSelections()
}
)
$table.on('all.bs.table', function (e, name, args) {
console.log(name, args)
})
$remove.click(function () {
var ids = getIdSelections()
$table.bootstrapTable('remove', {
field: 'ID',
values: ids
})
$remove.prop('disabled', true)
})
}
function ReadDetails(RecordID) {
$('#ID').val(RecordID);
console.log('✅ RecordID gesetzt in #ID:', RecordID);
$('#search_relation').data('record-id', RecordID);
getData(1, 1, RecordID);
}
function getData(number, size, idvalue) {
var Type = document.getElementById("authType").value;
if (typeof idvalue == "undefined") {
var search = $("div.bootstrap-table > div.fixed-table-toolbar > div.search > input.form-control").val();
var id = 0
} else {
var id = idvalue
var search = ''
}
$.get('/Thesaurus/ajax/getSubjectData.php', {
id: id,
search: search,
offset: (number - 1) * size,
authType: Type,
limit: size
}, function (res) {
$table.bootstrapTable('load', res)
})
}
$(function() {
$table.on('page-change.bs.table', function (e, number, size, id) {
getData(number, size, id)
})
var options = $table.bootstrapTable('getOptions')
getData(options.pageNumber, options.pageSize)
})
// DSpace-Counts asynchron laden nach dem Rendern der Tabelle
$(function() {
$table.on('post-body.bs.table', function () {
loadDSpaceCounts();
})
})
function loadDSpaceCounts() {
$('.dspace-count-cell').each(function() {
var $cell = $(this);
var term = $cell.data('term');
var authType = $cell.data('authtype');
if (!term || $cell.data('loaded')) return;
$cell.data('loaded', true);
$.ajax({
url: '/Thesaurus/ajax/getDSpaceCount.php',
type: 'GET',
data: { authType: authType, query: term },
dataType: 'json',
timeout: 15000,
success: function(data) {
var count = (data && typeof data.count !== 'undefined') ? data.count : 0;
if (count === -1) {
$cell.html('<span class="text-muted" title="DSpace nicht erreichbar"><i class="fas fa-exclamation-triangle" style="font-size: 0.75rem;"></i></span>');
} else if (count > 0) {
$cell.html('<span class="badge" style="background-color: #1a3a5c;" title="' + count + ' Datensätze in DSpace">' + count + '</span>');
} else {
$cell.html('<span class="badge bg-danger" title="Keine Datensätze in DSpace">0</span>');
}
},
error: function() {
$cell.html('<span class="text-muted" title="Fehler"><i class="fas fa-exclamation-triangle" style="font-size: 0.75rem;"></i></span>');
}
});
});
}
$(function() {
initTableSubject()
$('#locale').change(initTableSubject)
})
</script>
<!-- Custom JS file - muss vor dem Autocomplete-Script geladen werden -->
<script type="text/javascript" src="js/SubjectScript.js"></script>
<script>
// Globale searchRelation Funktion
window.searchRelation = function() {
const $input = $('#search_relation');
if ($input.length === 0) {
console.warn('⚠️ Element #search_relation nicht gefunden');
return;
}
// Entferne alte Event-Handler um Duplikate zu vermeiden
$input.off('input blur focus');
console.log('✓ Autocomplete initialisiert für:', $input.attr('id'));
let currentRequest = null;
$input.on('input', function() {
const query = $(this).val().trim();
console.log('🔍 Input erkannt:', query);
if (currentRequest) {
currentRequest.abort();
}
if (query.length < 2) {
console.log('⚠️ Zu kurz (minimum 2 Zeichen)');
$('.autocomplete-dropdown').remove();
return;
}
console.log('📡 Sende AJAX Anfrage...');
currentRequest = $.ajax({
url: '/Thesaurus/ajax/Autocomplete.php',
method: 'POST',
data: {
query: query,
authType: 'Subject'
},
dataType: 'json',
success: function(data) {
console.log('✅ Daten erhalten:', data.length, 'Einträge');
showAutocompleteResults(data, $input);
currentRequest = null;
},
error: function(xhr, status, error) {
if (status !== 'abort') {
console.error('❌ AJAX Fehler:', error);
}
currentRequest = null;
}
});
});
$input.on('blur', function() {
setTimeout(function() {
$('.autocomplete-dropdown').remove();
}, 200);
});
$input.on('focus', function() {
const query = $(this).val().trim();
if (query.length >= 2) {
$(this).trigger('input');
}
});
$(window).on('scroll resize', function() {
if ($('.autocomplete-dropdown').length > 0) {
const offset = $input.offset();
const inputHeight = $input.outerHeight();
const scrollTop = $(window).scrollTop();
const scrollLeft = $(window).scrollLeft();
$('.autocomplete-dropdown').css({
top: offset.top - scrollTop + inputHeight,
left: offset.left - scrollLeft
});
}
});
function showAutocompleteResults(data, $input) {
$('.autocomplete-dropdown').remove();
if (data.length === 0) {
console.log('⚠️ Keine Ergebnisse');
return;
}
const $dropdown = $('<div class="autocomplete-dropdown"></div>');
data.forEach(function(item) {
const match = item.match(/^(.+?)\s*\(ID:\s*(\d+)\)$/);
const label = match ? match[1].trim() : item;
const value = match ? match[2] : item;
const $item = $('<a class="dropdown-item" href="#"></a>')
.text(label)
.data('value', value)
.data('label', label)
.data('fulltext', item);
$item.on('click', function(e) {
e.preventDefault();
const selectedItem = {
label: $(this).data('label'),
value: $(this).data('value'),
fullText: $(this).data('fulltext')
};
console.log('✅ Ausgewählt:', selectedItem);
$input.val(selectedItem.label);
$input.data('selected-id', selectedItem.value);
$input.trigger('autocomplete:select', [selectedItem]);
$('.autocomplete-dropdown').remove();
// Speichere die Werte in Data-Attributen auf dem Button
const recordID = $('#ID').val();
const recordText = $('#new_term').val(); // Text des aktuellen Schlagworts
console.log('🔍 recordID:', recordID);
console.log('🔍 recordText:', recordText);
console.log('🔍 selectedItem.value:', selectedItem.value);
console.log('🔍 selectedItem.label:', selectedItem.label);
$('#subject-anlegen').data('record-id', recordID);
$('#subject-anlegen').data('record-text', recordText);
$('#subject-anlegen').data('relation-id', selectedItem.value);
$('#subject-anlegen').data('relation-text', selectedItem.label);
console.log('✅ Data-Attribute gesetzt auf #subject-anlegen');
});
$dropdown.append($item);
});
$('body').append($dropdown);
const offset = $input.offset();
const inputHeight = $input.outerHeight();
const inputWidth = $input.outerWidth();
const scrollTop = $(window).scrollTop();
const scrollLeft = $(window).scrollLeft();
$dropdown.css({
position: 'fixed',
top: offset.top - scrollTop + inputHeight,
left: offset.left - scrollLeft,
width: inputWidth,
'min-width': inputWidth,
'z-index': 9999
});
console.log('📋 Dropdown angezeigt mit', data.length, 'Einträgen');
}
};
// Automatische Initialisierung
$(document).ready(function() {
console.log('jQuery bereit');
$('.modal').on('shown.bs.modal', function () {
console.log('📂 Modal geöffnet');
if ($('#search_relation').length > 0) {
console.log('✅ search_relation gefunden, initialisiere...');
searchRelation();
}
});
if ($('#search_relation').length > 0) {
console.log('✅ search_relation beim Laden gefunden, initialisiere...');
searchRelation();
}
// Click-Handler für den "Hinzufügen" Button
$(document).on('click', '#subject-anlegen', function(event) {
event.preventDefault();
event.stopPropagation();
const recordID = $(this).data('record-id');
const recordText = $(this).data('record-text');
const relationID = $(this).data('relation-id');
const relationText = $(this).data('relation-text');
const relationType = $('#new_relationtype').val();
console.log('🚀 Button geklickt - recordID:', recordID, '- relationID:', relationID, '- relationType:', relationType);
if (recordID && relationID) {
// Bei Synonym-Typ: andere Speicherfunktion verwenden
if (relationType === 'SYN') {
CreateNewSynonym(recordText, relationText, recordID, event);
} else {
CreateNewRelation(recordID, relationID, event);
}
} else {
console.warn('⚠️ recordID oder relationID fehlt!');
alert('Bitte zuerst einen Begriff aus der Autocomplete-Liste auswählen.');
}
});
});
</script>
<!-- Bootstrap Modals -->
<?php
$fp = fopen("templates/SubjectsModal.tpl", "r");
if ($fp) {
echo (fread($fp, filesize("templates/SubjectsModal.tpl")));
fclose($fp);
}
?>
<?php
$fp = fopen("templates/SubjectDetailsModal.tpl", "r");
if ($fp) {
echo (fread($fp, filesize("templates/SubjectDetailsModal.tpl")));
fclose($fp);
}
?>
<?php
$fp = fopen("templates/newSubject.tpl", "r");
if ($fp) {
echo (fread($fp, filesize("templates/newSubject.tpl")));
fclose($fp);
}
?>
<?php
$fp = fopen("templates/RelationsModal.tpl", "r");
if ($fp) {
echo (fread($fp, filesize("templates/RelationsModal.tpl")));
fclose($fp);
}
?>
<?php
include "templates/Footer.html";
?>

671
Treeview.php Normal file
View File

@ -0,0 +1,671 @@
<?php
include "templates/Header.html";
?>
<!-- Hidden Fields -->
<input type="hidden" id="locale" value="de-DE">
<input type="hidden" id="ID" value="">
<input type="hidden" id="authType" value="Classification">
<!-- jsTree CSS -->
<link rel="stylesheet" href="/libs/jstree/current/themes/default/style.min.css" onerror="this.onerror=null; this.href='https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.16/themes/default/style.min.css';" />
<style>
/* Treeview Container */
.treeview-container {
display: flex;
gap: 20px;
min-height: 600px;
align-items: flex-start;
}
.tree-panel {
flex: 3;
min-width: 0;
}
.detail-panel {
flex: 2;
min-width: 0;
position: sticky;
top: 20px;
}
/* Tree Styling */
#classification-tree {
padding: 15px;
background: var(--bg-light);
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
min-height: 500px;
max-height: 75vh;
overflow-x: auto;
overflow-y: auto;
white-space: nowrap;
}
/* jsTree Anpassungen für BIBB-Blau - verstärkt */
.jstree-default .jstree-clicked,
.jstree-default .jstree-wholerow-clicked {
background: #1a3a5c !important;
color: white !important;
border-radius: 4px;
box-shadow: none !important;
}
.jstree-default .jstree-clicked .jstree-icon,
.jstree-default .jstree-wholerow-clicked + .jstree-anchor .jstree-icon {
color: white !important;
}
.jstree-default .jstree-hovered,
.jstree-default .jstree-wholerow-hovered {
background: #e9ecef !important;
border-radius: 4px;
box-shadow: none !important;
}
.jstree-default .jstree-anchor {
color: var(--text-primary);
padding: 4px 8px;
white-space: nowrap;
max-width: none;
overflow: visible;
}
.jstree-default .jstree-icon {
color: var(--primary-color);
}
/* Detail Panel */
.detail-card {
background: var(--bg-light);
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
padding: 20px;
min-height: 500px;
}
.detail-card h4 {
color: var(--primary-color);
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid var(--primary-color);
}
.detail-row {
display: flex;
margin-bottom: 15px;
}
.detail-label {
font-weight: 600;
color: var(--text-muted);
width: 120px;
flex-shrink: 0;
}
.detail-value {
color: var(--text-primary);
flex: 1;
}
.no-selection {
text-align: center;
color: var(--text-muted);
padding: 50px 20px;
}
.no-selection i {
font-size: 48px;
margin-bottom: 15px;
display: block;
opacity: 0.5;
}
/* Toolbar */
.tree-toolbar {
display: flex;
gap: 10px;
margin-bottom: 15px;
flex-wrap: wrap;
}
.tree-toolbar .btn {
display: flex;
align-items: center;
gap: 5px;
}
/* Search */
.tree-search {
position: relative;
flex: 1;
min-width: 200px;
}
.tree-search input {
width: 100%;
padding-left: 35px;
}
.tree-search i.fa-search {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: var(--text-muted);
z-index: 10;
}
/* Suchergebnisse Dropdown */
.search-results {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
max-height: 300px;
overflow-y: auto;
z-index: 1000;
display: none;
}
.search-results.show {
display: block;
}
.search-result-item {
padding: 10px 15px;
cursor: pointer;
border-bottom: 1px solid var(--border-light);
}
.search-result-item:hover {
background: var(--bg-light);
}
.search-result-item:last-child {
border-bottom: none;
}
.search-result-item .result-text {
font-weight: 500;
color: var(--text-primary);
}
.search-result-item .result-id {
font-size: 12px;
color: var(--text-muted);
}
.search-no-results {
padding: 15px;
text-align: center;
color: var(--text-muted);
}
/* Responsive */
@media (max-width: 1200px) {
.treeview-container {
flex-direction: column;
}
.tree-panel, .detail-panel {
min-width: 100%;
flex: none
}
.tree-panel, .detail-panel {
min-width: 100%;
flex: none;
}
.detail-panel {
position: static; /* kein sticky bei gestapelter Ansicht */
}
}
</style>
<!-- Main Content -->
<main class="main-content">
<div class="container">
<!-- Content Card -->
<div class="card">
<div class="d-flex justify-between align-center mb-20">
<h1 class="card-title" style="margin: 0;">🌳 Klassifikationsbaum</h1>
</div>
<!-- Toolbar -->
<div class="tree-toolbar">
<div class="tree-search">
<i class="fas fa-search"></i>
<input type="text" id="tree-search-input" class="form-control" placeholder="Klassifikation suchen..." autocomplete="off" />
<div id="search-results" class="search-results"></div>
</div>
<button class="btn btn-secondary" id="btn-expand-all" title="Alle aufklappen">
<i class="fas fa-expand-alt"></i> Alle öffnen
</button>
<button class="btn btn-secondary" id="btn-collapse-all" title="Alle zuklappen">
<i class="fas fa-compress-alt"></i> Alle schließen
</button>
<button class="btn btn-secondary" id="btn-refresh" title="Aktualisieren">
<i class="fas fa-sync"></i>
</button>
</div>
<!-- Treeview Container -->
<div class="treeview-container">
<!-- Tree Panel -->
<div class="tree-panel">
<div id="classification-tree"></div>
</div>
<!-- Detail Panel -->
<div class="detail-panel">
<div class="detail-card">
<div id="detail-content">
<div class="no-selection">
<i class="fas fa-hand-pointer"></i>
<p>Wählen Sie eine Klassifikation aus dem Baum aus, um Details anzuzeigen.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<!-- jsTree JS -->
<script src="/libs/jstree/current/jstree.min.js"></script>
<script>if (typeof $.jstree === "undefined") { document.write('<script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.16/jstree.min.js"><\/script>'); }</script>
<script>
$(document).ready(function() {
// Tree initialisieren
initTree();
// Toolbar Events
$('#btn-expand-all').on('click', function() {
$('#classification-tree').jstree('open_all');
});
$('#btn-collapse-all').on('click', function() {
$('#classification-tree').jstree('close_all');
});
$('#btn-refresh').on('click', function() {
$('#classification-tree').jstree('refresh');
});
// Serverseitige Suche
var searchTimeout = null;
$('#tree-search-input').on('keyup', function() {
var searchString = $(this).val().trim();
if (searchTimeout) {
clearTimeout(searchTimeout);
}
if (searchString.length < 2) {
$('#search-results').removeClass('show').empty();
return;
}
searchTimeout = setTimeout(function() {
performSearch(searchString);
}, 300);
});
// Suche schließen bei Klick außerhalb
$(document).on('click', function(e) {
if (!$(e.target).closest('.tree-search').length) {
$('#search-results').removeClass('show');
}
});
// Focus auf Suchfeld zeigt Ergebnisse wieder
$('#tree-search-input').on('focus', function() {
if ($('#search-results').children().length > 0) {
$('#search-results').addClass('show');
}
});
});
function initTree() {
$('#classification-tree').jstree({
'core': {
'data': {
'url': function(node) {
return '/Thesaurus/ajax/getTreeData.php';
},
'data': function(node) {
return {
'id': node.id
};
}
},
'themes': {
'name': 'default',
'responsive': true,
'dots': true,
'icons': true
},
'multiple': false,
'animation': 200
},
'plugins': ['wholerow', 'sort'],
'sort': function(a, b) {
var nodeA = this.get_text(a);
var nodeB = this.get_text(b);
return nodeA.localeCompare(nodeB, 'de', {numeric: true});
}
});
// Node ausgewählt
$('#classification-tree').on('select_node.jstree', function(e, data) {
var node = data.node;
loadDetails(node.id, node.text, node.data);
});
// Tree geladen
$('#classification-tree').on('loaded.jstree', function() {
console.log('✅ Klassifikationsbaum geladen');
});
}
function performSearch(searchString) {
console.log('🔍 Suche nach:', searchString);
$.get('/Thesaurus/ajax/searchTreeData.php', {
q: searchString
}, function(data) {
var results = typeof data === 'string' ? JSON.parse(data) : data;
var html = '';
if (results && results.length > 0) {
results.forEach(function(item) {
html += '<div class="search-result-item" data-id="' + item.id + '">';
html += '<div class="result-text">' + escapeHtml(item.text) + '</div>';
html += '<div class="result-id">ID: ' + item.id + '</div>';
html += '</div>';
});
} else {
html = '<div class="search-no-results"><i class="fas fa-search"></i> Keine Ergebnisse gefunden</div>';
}
$('#search-results').html(html).addClass('show');
// Klick auf Suchergebnis
$('.search-result-item').on('click', function() {
var id = $(this).data('id');
selectAndShowNode(id);
$('#search-results').removeClass('show');
$('#tree-search-input').val('');
});
}).fail(function() {
$('#search-results').html('<div class="search-no-results">Fehler bei der Suche</div>').addClass('show');
});
}
// PATCH für Treeview.php Funktion selectAndShowNode ersetzen (v3)
// Ersetzt die bisherige selectAndShowNode() und activateNode() komplett.
function selectAndShowNode(id) {
id = String(id);
$.get('/Thesaurus/ajax/getTreePath.php', { id: id }, function(path) {
if (!path || path.length === 0) {
console.warn('⚠️ Kein Pfad gefunden für ID:', id);
loadDetails(id, '', {});
return;
}
console.log('📂 Pfad zum Knoten:', path);
// Vorfahren = alle Elemente außer dem Zielknoten selbst
var ancestors = path.slice(0, -1).map(String);
var targetId = String(path[path.length - 1]);
// Sequenziell öffnen: erst wenn ein Knoten vollständig geöffnet
// (und seine Kinder geladen) sind, wird der nächste geöffnet.
openSequential(ancestors, 0, function() {
activateNode(targetId);
});
}).fail(function() {
console.error('❌ getTreePath.php fehlgeschlagen');
loadDetails(id, '', {});
});
}
/**
* Öffnet Knoten aus dem ancestors-Array nacheinander.
* Wartet nach jedem open_node auf after_open.jstree bevor weitergemacht wird.
* Knoten die bereits offen sind werden übersprungen.
*/
function openSequential(ancestors, index, callback) {
if (index >= ancestors.length) {
callback();
return;
}
var tree = $('#classification-tree').jstree(true);
var nodeId = ancestors[index];
var node = tree.get_node(nodeId);
// Knoten bereits offen oder nicht auffindbar → nächster
if (!node || node.state.opened) {
console.log('⏭️ Überspringe (bereits offen oder nicht geladen):', nodeId);
openSequential(ancestors, index + 1, callback);
return;
}
console.log('🔓 Öffne Knoten:', nodeId);
// Einmaliger after_open-Handler nur für diesen Knoten
function onOpen(e, data) {
if (String(data.node.id) !== nodeId) return; // anderer Knoten
$('#classification-tree').off('after_open.jstree', onOpen);
console.log('✅ Geöffnet:', nodeId);
openSequential(ancestors, index + 1, callback);
}
$('#classification-tree').on('after_open.jstree', onOpen);
// Sicherheits-Timeout pro Knoten (3 Sekunden)
setTimeout(function() {
$('#classification-tree').off('after_open.jstree', onOpen);
console.warn('⏱️ Timeout beim Öffnen von:', nodeId);
openSequential(ancestors, index + 1, callback);
}, 3000);
tree.open_node(nodeId);
}
/**
* Wählt den Zielknoten aus und scrollt ihn in Sicht.
*/
function activateNode(id) {
var tree = $('#classification-tree').jstree(true);
console.log('🎯 Aktiviere Knoten:', id);
tree.deselect_all();
tree.select_node(id);
setTimeout(function() {
var anchor = document.getElementById(id + '_anchor');
if (anchor) {
anchor.scrollIntoView({ behavior: 'smooth', block: 'center' });
console.log('📜 Gescrollt zu:', id);
} else {
console.warn('⚠️ Anchor nicht gefunden für ID:', id);
}
}, 200);
}
function loadDetails(id, text, nodeData) {
console.log('📖 Lade Details für ID:', id);
// Loading anzeigen
$('#detail-content').html('<div class="text-center p-4"><div class="spinner-border text-primary" role="status"></div><p class="mt-2">Laden...</p></div>');
$.get('/Thesaurus/ajax/getDetailAuthorityData.php', {
authType: 'Classification',
id: id,
offset: 0,
limit: 1
}, function(data, status) {
if (status === 'success') {
try {
var metadata = typeof data === 'string' ? JSON.parse(data) : data;
var row = metadata['rows'] ? metadata['rows'][0] : null;
if (row) {
renderDetails(row);
} else {
// Fallback: Zeige zumindest die Basis-Infos aus dem Baum
renderBasicDetails(id, text, nodeData);
}
} catch (e) {
console.error('❌ Parse-Fehler:', e);
renderBasicDetails(id, text, nodeData);
}
}
}).fail(function() {
renderBasicDetails(id, text, nodeData);
});
}
function renderDetails(row) {
var html = '';
html += '<h4><i class="fas fa-folder-tree me-2"></i>' + escapeHtml(row.Text || row.name || 'Unbekannt') + '</h4>';
html += '<div class="detail-row">';
html += '<span class="detail-label">ID:</span>';
html += '<span class="detail-value">' + escapeHtml(row.ID || '') + '</span>';
html += '</div>';
if (row.Notation) {
html += '<div class="detail-row">';
html += '<span class="detail-label">Notation:</span>';
html += '<span class="detail-value"><strong>' + escapeHtml(row.Notation) + '</strong></span>';
html += '</div>';
}
/* if (row.Descriptor) {
html += '<div class="detail-row">';
html += '<span class="detail-label">Deskriptor:</span>';
html += '<span class="detail-value">' + escapeHtml(row.Descriptor) + '</span>';
html += '</div>';
}
*/
if (row.Type) {
html += '<div class="detail-row">';
html += '<span class="detail-label">Typ:</span>';
html += '<span class="detail-value">' + escapeHtml(row.Type) + '</span>';
html += '</div>';
}
if (row.Scopenote) {
html += '<div class="detail-row">';
html += '<span class="detail-label">Anmerkung:</span>';
html += '<span class="detail-value">' + escapeHtml(row.Scopenote) + '</span>';
html += '</div>';
}
// Relationen
if (row.Relations && row.Relations.length > 0) {
html += '<hr>';
html += '<h5><i class="fas fa-link me-2"></i>Relationen</h5>';
html += '<div class="relations-list">';
row.Relations.forEach(function(rel) {
html += '<div class="relation-item" style="display: flex; align-items: center; padding: 8px 12px; background: white; border-radius: 4px; margin-bottom: 8px; border: 1px solid #dee2e6;">';
html += '<span style="font-weight: 600; color: var(--primary-color); width: 80px; flex-shrink: 0;">' + escapeHtml(rel.Relationtype || '') + '</span>';
html += '<span style="flex: 1;">';
html += '<a href="javascript:void(0)" onclick="selectAndShowNode(\'' + rel.IDRelation + '\')" style="color: var(--primary-color); cursor: pointer;">' + escapeHtml(rel.TextRelation || '') + '</a>';
html += ' <small class="text-muted">(ID: ' + escapeHtml(rel.IDRelation || '') + ')</small>';
html += '</span>';
html += '</div>';
});
html += '</div>';
}
// Aktionen
html += '<hr>';
html += '<div class="d-flex gap-2">';
html += '<button class="btn btn-primary btn-sm" onclick="editClassification(\'' + row.ID + '\')"><i class="fas fa-edit me-1"></i>Bearbeiten</button>';
html += '<button class="btn btn-secondary btn-sm" onclick="copyToClipboard(\'' + escapeHtml(row.Text || '') + '\')"><i class="fas fa-copy me-1"></i>Name kopieren</button>';
html += '</div>';
$('#detail-content').html(html);
}
function renderBasicDetails(id, text, nodeData) {
var html = '';
html += '<h4><i class="fas fa-folder-tree me-2"></i>' + escapeHtml(text || 'Klassifikation') + '</h4>';
html += '<div class="detail-row">';
html += '<span class="detail-label">ID:</span>';
html += '<span class="detail-value">' + escapeHtml(id) + '</span>';
html += '</div>';
if (nodeData && nodeData.name) {
html += '<div class="detail-row">';
html += '<span class="detail-label">Name:</span>';
html += '<span class="detail-value">' + escapeHtml(nodeData.name) + '</span>';
html += '</div>';
}
html += '<hr>';
html += '<div class="d-flex gap-2">';
html += '<button class="btn btn-primary btn-sm" onclick="editClassification(\'' + id + '\')"><i class="fas fa-edit me-1"></i>Bearbeiten</button>';
html += '<button class="btn btn-secondary btn-sm" onclick="copyToClipboard(\'' + escapeHtml(text || '') + '\')"><i class="fas fa-copy me-1"></i>Name kopieren</button>';
html += '</div>';
$('#detail-content').html(html);
}
function editClassification(id) {
window.location.href = 'Classifications.php?edit=' + id;
}
function copyToClipboard(text) {
var tempInput = document.createElement('input');
tempInput.value = text;
document.body.appendChild(tempInput);
tempInput.select();
document.execCommand('copy');
document.body.removeChild(tempInput);
// Feedback
alert('"' + text + '" wurde in die Zwischenablage kopiert.');
}
function escapeHtml(text) {
if (!text) return '';
var div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
</script>
<?php
include "templates/Footer.html";
?>

31
ajax/Autocomplete.php Normal file
View File

@ -0,0 +1,31 @@
<?php
/**
* Autocomplete.php - Liefert Vorschläge für Autocomplete-Felder
*/
include "db_connection.php";
require_once "libAuthorities.php";
$request = isset($_POST['query']) ? strtolower($_POST["query"]) : '';
$authType = isset($_POST['authType']) ? $_POST['authType'] : 'Subject';
if (empty($request)) {
echo json_encode([]);
exit();
}
try {
$object = new CRUD();
$response = $object->getEntryText($authType, $request);
$data = [];
foreach ($response as $entry) {
$data[] = $entry['Text'] . " (ID: " . $entry['ID'] . ")";
}
echo json_encode($data);
} catch (Exception $e) {
echo json_encode([]);
}
?>

84
ajax/UpdateTerm.php Normal file
View File

@ -0,0 +1,84 @@
<?php
/**
* UpdateTerm.php - Aktualisiert einen bestehenden Normdaten-Eintrag
*
* Parameter (POST) - von JavaScript:
* - AnchorID: ID des Eintrags
* - authType: Typ (Subject, Person, etc.)
* - term: Neuer Anzeigetext
* - detailtype: Neuer Untertyp (optional)
* - classification: Neuer Klassifikationscode (optional)
* - scopenote: Neuer Kommentar (optional)
*/
header('Content-Type: application/json; charset=utf-8');
include "db_connection.php";
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
if (mysqli_connect_errno()) {
echo json_encode(['success' => false, 'error' => 'Datenbankverbindung fehlgeschlagen']);
exit();
}
mysqli_set_charset($conn, "utf8");
// Parameter aus JavaScript
$id = isset($_POST['AnchorID']) ? intval($_POST['AnchorID']) : 0;
if ($id === 0) {
$id = isset($_POST['ID']) ? intval($_POST['ID']) : 0;
}
$text = isset($_POST['term']) ? trim($_POST['term']) : '';
$detailType = isset($_POST['detailtype']) ? trim($_POST['detailtype']) : '';
$classification = isset($_POST['classification']) ? trim($_POST['classification']) : '';
$comment = isset($_POST['scopenote']) ? trim($_POST['scopenote']) : '';
if ($id === 0) {
echo json_encode(['success' => false, 'error' => 'ID muss angegeben werden']);
exit();
}
if (empty($text)) {
echo json_encode(['success' => false, 'error' => 'Text darf nicht leer sein']);
exit();
}
mysqli_begin_transaction($conn);
try {
// 1. Anchor aktualisieren (Text, DetailType, Classification)
$updateAnchorSql = "UPDATE Anchor SET
Text = '" . mysqli_real_escape_string($conn, $text) . "',
DetailType = '" . mysqli_real_escape_string($conn, $detailType) . "',
Classification = '" . mysqli_real_escape_string($conn, $classification) . "'
WHERE ID = $id";
if (!mysqli_query($conn, $updateAnchorSql)) {
throw new Exception('Fehler beim Aktualisieren des Anchor: ' . mysqli_error($conn));
}
// 2. Entry aktualisieren
$updateEntrySql = "UPDATE Entry SET
Text = '" . mysqli_real_escape_string($conn, $text) . "',
CompleteText = '" . mysqli_real_escape_string($conn, $text) . "',
Comment = '" . mysqli_real_escape_string($conn, $comment) . "'
WHERE ID = $id";
if (!mysqli_query($conn, $updateEntrySql)) {
throw new Exception('Fehler beim Aktualisieren des Entry: ' . mysqli_error($conn));
}
mysqli_commit($conn);
echo json_encode([
'success' => true,
'message' => 'Eintrag erfolgreich aktualisiert'
]);
} catch (Exception $e) {
mysqli_rollback($conn);
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
mysqli_close($conn);
?>

View File

@ -0,0 +1,27 @@
<?php
/**
* Datenbankverbindung für BIBB Thesaurus
* Kopiere diese Datei als db_connection.php und trage deine Zugangsdaten ein.
*/
define('HOST', 'localhost');
define('USER', 'DEIN_DATENBANKBENUTZER');
define('PASSWORD', 'DEIN_PASSWORT');
define('DATABASE', 'TSData');
define('CHARSET', 'utf8');
function DB()
{
static $instance;
if ($instance === null) {
$opt = array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => FALSE,
);
$dsn = 'mysql:host=' . HOST . ';dbname=' . DATABASE . ';charset=' . CHARSET;
$instance = new PDO($dsn, USER, PASSWORD, $opt);
}
return $instance;
}
?>

86
ajax/deleteRelation.php Normal file
View File

@ -0,0 +1,86 @@
<?php
/**
* deleteRelation.php - Löscht eine Relation
*
* Parameter (POST):
* - IDAnchor: ID des Ausgangseintrags
* - LinkingID: ID der Relation in der Linking-Tabelle
*/
header('Content-Type: application/json; charset=utf-8');
include "db_connection.php";
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
if (mysqli_connect_errno()) {
echo json_encode(['success' => false, 'error' => 'Datenbankverbindung fehlgeschlagen']);
exit();
}
mysqli_set_charset($conn, "utf8");
$idAnchor = isset($_POST['IDAnchor']) ? intval($_POST['IDAnchor']) : 0;
$linkingId = isset($_POST['LinkingID']) ? intval($_POST['LinkingID']) : 0;
if ($linkingId === 0) {
echo json_encode(['success' => false, 'error' => 'LinkingID muss angegeben werden']);
exit();
}
// Zuerst Relation lesen für reziproke Löschung
$getSql = "SELECT IDAnchor, IDEntry, Relationtype FROM Linking WHERE ID = $linkingId";
$getResult = mysqli_query($conn, $getSql);
if (!$getResult || mysqli_num_rows($getResult) === 0) {
echo json_encode(['success' => false, 'error' => 'Relation nicht gefunden']);
mysqli_close($conn);
exit();
}
$relation = mysqli_fetch_assoc($getResult);
$targetId = $relation['IDEntry'];
$relationType = $relation['Relationtype'];
// Reziproke Relation bestimmen
$reciprocalType = null;
switch ($relationType) {
case 'BT':
$reciprocalType = 'NT';
break;
case 'NT':
$reciprocalType = 'BT';
break;
case 'RT':
$reciprocalType = 'RT';
break;
case 'USE':
$reciprocalType = 'UF';
break;
case 'UF':
$reciprocalType = 'USE';
break;
}
// Relation löschen
$deleteSql = "DELETE FROM Linking WHERE ID = $linkingId";
if (mysqli_query($conn, $deleteSql)) {
$affectedRows = mysqli_affected_rows($conn);
// Reziproke Relation ebenfalls löschen
if ($reciprocalType) {
$deleteRecSql = "DELETE FROM Linking WHERE IDAnchor = $targetId AND IDEntry = " . $relation['IDAnchor'] . " AND Relationtype = '$reciprocalType'";
mysqli_query($conn, $deleteRecSql);
}
echo json_encode([
'success' => true,
'message' => 'Relation erfolgreich gelöscht',
'affected' => $affectedRows
]);
} else {
echo json_encode(['success' => false, 'error' => 'Fehler beim Löschen: ' . mysqli_error($conn)]);
}
mysqli_close($conn);
?>

59
ajax/deleteTerm.php Normal file
View File

@ -0,0 +1,59 @@
<?php
/**
* deleteTerm.php - Löscht einen Normdaten-Eintrag
*
* Parameter (POST):
* - AnchorID: ID des Eintrags (primär, gesendet von SubjectScript.js)
* - ID: ID des Eintrags (alternativ, für Rückwärtskompatibilität)
* - authType: Typ (Subject, Person, Corporate, Publisher, Classification)
*/
header('Content-Type: application/json; charset=utf-8');
include "db_connection.php";
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
if (mysqli_connect_errno()) {
echo json_encode(['success' => false, 'error' => 'Datenbankverbindung fehlgeschlagen']);
exit();
}
mysqli_set_charset($conn, "utf8");
// Parameter: AnchorID (von JS) oder ID (Fallback)
$id = 0;
if (isset($_POST['AnchorID']) && intval($_POST['AnchorID']) > 0) {
$id = intval($_POST['AnchorID']);
} elseif (isset($_POST['ID']) && intval($_POST['ID']) > 0) {
$id = intval($_POST['ID']);
}
if ($id === 0) {
echo json_encode(['success' => false, 'error' => 'Gültige ID muss angegeben werden']);
exit();
}
// Transaktion starten
mysqli_begin_transaction($conn);
try {
// 1. Alle Relationen löschen (als Anchor und als Entry)
mysqli_query($conn, "DELETE FROM Linking WHERE IDAnchor = $id");
mysqli_query($conn, "DELETE FROM Linking WHERE IDEntry = $id");
// 2. Entry löschen
mysqli_query($conn, "DELETE FROM Entry WHERE ID = $id");
// 3. Anchor löschen
mysqli_query($conn, "DELETE FROM Anchor WHERE ID = $id");
mysqli_commit($conn);
echo json_encode([
'success' => true,
'message' => 'Eintrag erfolgreich gelöscht'
]);
} catch (Exception $e) {
mysqli_rollback($conn);
echo json_encode(['success' => false, 'error' => 'Fehler beim Löschen: ' . $e->getMessage()]);
}
mysqli_close($conn);
?>

167
ajax/getAuthorityData.php Normal file
View File

@ -0,0 +1,167 @@
<?php
/**
* getAuthorityData.php - Liefert Normdaten für Bootstrap Table
*
* Parameter (Bootstrap Table Server-Side):
* - limit: Anzahl Einträge pro Seite
* - offset: Start-Position
* - search: Suchbegriff
* - authType: Typ (Subject, Person, Corporate, Publisher, Classification)
*
* Rückgabe: JSON mit total und rows
*/
header('Content-Type: application/json; charset=utf-8');
include "db_connection.php";
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
if (mysqli_connect_errno()) {
echo json_encode(['total' => 0, 'rows' => [], 'error' => 'Datenbankverbindung fehlgeschlagen']);
exit();
}
mysqli_set_charset($conn, "utf8");
// Parameter auslesen
$limit = isset($_GET['limit']) ? $_GET['limit'] : 25;
$offset = isset($_GET['offset']) ? intval($_GET['offset']) : 0;
// "all" oder 0 bedeutet: alle Treffer ohne Limit
if ($limit === 'all' || $limit === '0' || intval($limit) <= 0) {
$limitAll = true;
$limit = 0;
} else {
$limitAll = false;
$limit = intval($limit);
}
$search = isset($_GET['search']) ? trim($_GET['search']) : '';
$authType = isset($_GET['authType']) ? trim($_GET['authType']) : 'Subject';
// Erlaubte Typen
$allowedTypes = ['Subject', 'Person', 'Corporate', 'Publisher', 'Classification'];
if (!in_array($authType, $allowedTypes)) {
$authType = 'Subject';
}
// Basis-SQL
$baseSql = "FROM Anchor a
LEFT JOIN Entry e ON a.ID = e.ID
WHERE a.Type = '" . mysqli_real_escape_string($conn, $authType) . "'";
// Suchbedingung hinzufügen (case-insensitive)
// Scope Note (e.Comment) wird bewusst nicht durchsucht
if (!empty($search)) {
// Trunkierung auswerten
$leftTrunc = (substr($search, 0, 1) === '*');
$rightTrunc = (substr($search, -1) === '*');
// Sternchen entfernen, dann escapen
$clean = trim($search, '*');
$searchEscaped = mysqli_real_escape_string($conn, $clean);
// LIKE-Sonderzeichen in den Daten escapen
$searchEscaped = str_replace(['%', '_'], ['\\%', '\\_'], $searchEscaped);
// Pattern bauen
$pattern = ($leftTrunc ? '%' : '') .
$searchEscaped .
($rightTrunc ? '%' : '%'); // rechts immer trunkieren = bisheriges Verhalten
$baseSql .= " AND (LOWER(e.Text) LIKE LOWER('{$pattern}')
OR LOWER(e.CompleteText) LIKE LOWER('{$pattern}'))";
}
// Gesamtanzahl ermitteln
$countSql = "SELECT COUNT(*) as total " . $baseSql;
$countResult = mysqli_query($conn, $countSql);
$total = 0;
if ($countResult) {
$countRow = mysqli_fetch_assoc($countResult);
$total = intval($countRow['total']);
}
// ORDER BY je nach authType
// Classification: numerisch nach Notation (z.B. T 4.10 nach T 4.9)
// Alle anderen: deutsch-korrekt nach Unicode-Kollation (Umlaute, Groß/Klein, Sonderzeichen)
if ($authType === 'Classification') {
// Dreistufige Sortierung:
// 1. Buchstabenpräfix alphabetisch (G, S, T ...)
// 2. Erste Zahl numerisch (Hauptgruppe vor Untergruppe: NULL kommt zuerst)
// 3. Zweite Zahl numerisch (T 4.9 vor T 4.10)
$orderClause = "
SUBSTRING_INDEX(a.Classification, ' ', 1),
CAST(
NULLIF(SUBSTRING_INDEX(SUBSTRING_INDEX(a.Classification, ' ', -1), '.', 1),
a.Classification)
AS UNSIGNED
),
CAST(
NULLIF(SUBSTRING_INDEX(a.Classification, '.', -1),
a.Classification)
AS UNSIGNED
)
";
} else {
// utf8_unicode_ci: Umlaute korrekt (Ä=A, Ö=O, Ü=U),
// Sonderzeichen nach Grundbuchstabe, Groß-/Klein ignoriert
$orderClause = "e.Text COLLATE utf8_unicode_ci ASC";
}
// Daten abrufen
$dataSql = "SELECT a.ID, a.Type, a.DetailType, a.Classification,
e.Text, e.CompleteText, e.Comment,
e.DateCreated, e.DateModified " . $baseSql . "
ORDER BY " . $orderClause;
if (!$limitAll) {
$dataSql .= " LIMIT $offset, $limit";
}
$dataResult = mysqli_query($conn, $dataSql);
$rows = [];
if ($dataResult) {
while ($row = mysqli_fetch_assoc($dataResult)) {
// Relationen zählen
$relSql = "SELECT COUNT(*) as cnt FROM Linking WHERE IDAnchor = " . intval($row['ID']);
$relResult = mysqli_query($conn, $relSql);
$relCount = 0;
if ($relResult) {
$relRow = mysqli_fetch_assoc($relResult);
$relCount = intval($relRow['cnt']);
}
// Deskriptor-Status ermitteln (USE-Relation = Non-Deskriptor)
$useSql = "SELECT COUNT(*) as cnt FROM Linking WHERE IDAnchor = " . intval($row['ID']) . " AND UPPER(Relationtype) = 'USE'";
$useResult = mysqli_query($conn, $useSql);
$isDescriptor = true;
if ($useResult) {
$useRow = mysqli_fetch_assoc($useResult);
if (intval($useRow['cnt']) > 0) {
$isDescriptor = false;
}
}
$rows[] = [
'ID' => $row['ID'],
'Type' => $row['Type'],
'DetailType' => $row['DetailType'] ?? '',
'Classification' => $row['Classification'] ?? '',
'Text' => $row['Text'] ?? '',
'CompleteText' => $row['CompleteText'] ?? '',
'Comment' => $row['Comment'] ?? '',
'DateCreated' => $row['DateCreated'] ?? '',
'DateModified' => $row['DateModified'] ?? '',
'RelationCount' => $relCount,
'Descriptor' => $isDescriptor
];
}
}
mysqli_close($conn);
echo json_encode([
'total' => $total,
'rows' => $rows
], JSON_UNESCAPED_UNICODE);
?>

View File

@ -0,0 +1,111 @@
<?php
/**
* getAuthorityDataRaw.php - Liefert Daten für Autocomplete-Suche ODER einzelnen Eintrag per ID
*
* Parameter:
* - id: ID eines einzelnen Eintrags (für ModifyTerm)
* - search: Suchbegriff für Autocomplete (min. 2 Zeichen)
* - authType: Typ (Subject, Person, Corporate, Publisher, Classification)
* - limit: Max. Anzahl Ergebnisse (Standard: 10)
*/
header('Content-Type: application/json; charset=utf-8');
include "db_connection.php";
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
if (mysqli_connect_errno()) {
echo json_encode(['rows' => []]);
exit();
}
mysqli_set_charset($conn, "utf8");
// Prüfen ob ID-Abfrage (für ModifyTerm)
$id = isset($_GET['id']) ? intval($_GET['id']) : 0;
if ($id > 0) {
// Einzelnen Eintrag per ID laden
$sql = "SELECT a.ID, a.Type, a.DetailType, a.Classification,
e.Text, e.CompleteText, e.Comment as Scopenote
FROM Anchor a
LEFT JOIN Entry e ON a.ID = e.ID
WHERE a.ID = $id";
$result = mysqli_query($conn, $sql);
if ($result && mysqli_num_rows($result) > 0) {
$row = mysqli_fetch_assoc($result);
// Descriptor erstellen
$descriptor = strtolower($row['Text'] ?? '');
$descriptor = str_replace(' ', '_', $descriptor);
echo json_encode([
'rows' => [[
'ID' => $row['ID'],
'Type' => $row['Type'],
'DetailType' => $row['DetailType'] ?? '',
'Classification' => $row['Classification'] ?? '',
'Text' => $row['Text'] ?? '',
'CompleteText' => $row['CompleteText'] ?? '',
'Scopenote' => $row['Scopenote'] ?? '',
'Descriptor' => $descriptor
]]
], JSON_UNESCAPED_UNICODE);
} else {
echo json_encode(['rows' => []]);
}
mysqli_close($conn);
exit();
}
// Sonst: Autocomplete-Suche
$search = isset($_GET['search']) ? trim($_GET['search']) : '';
$authType = isset($_GET['authType']) ? trim($_GET['authType']) : '';
$limit = isset($_GET['limit']) ? intval($_GET['limit']) : 10;
if (strlen($search) < 2) {
echo json_encode([]);
exit();
}
// Erlaubte Typen
$allowedTypes = ['Subject', 'Person', 'Corporate', 'Publisher', 'Classification'];
// SQL aufbauen
$searchEscaped = mysqli_real_escape_string($conn, $search);
$sql = "SELECT a.ID, a.Type, a.DetailType, e.Text, e.CompleteText
FROM Anchor a
LEFT JOIN Entry e ON a.ID = e.ID
WHERE (e.Text LIKE '%{$searchEscaped}%' OR e.CompleteText LIKE '%{$searchEscaped}%')";
// Typ-Filter falls angegeben
if (!empty($authType) && in_array($authType, $allowedTypes)) {
$sql .= " AND a.Type = '" . mysqli_real_escape_string($conn, $authType) . "'";
}
$sql .= " ORDER BY e.Text ASC LIMIT $limit";
$result = mysqli_query($conn, $sql);
$data = [];
if ($result) {
while ($row = mysqli_fetch_assoc($result)) {
$data[] = [
'id' => $row['ID'],
'value' => $row['Text'],
'text' => $row['Text'],
'type' => $row['Type'],
'detailType' => $row['DetailType'] ?? '',
'completeText' => $row['CompleteText'] ?? ''
];
}
}
mysqli_close($conn);
echo json_encode($data, JSON_UNESCAPED_UNICODE);
?>

View File

@ -0,0 +1,8 @@
<?php
/**
* getClassificationData.php - Wrapper für getAuthorityData.php mit authType=Classification
*/
$_GET['authType'] = 'Classification';
include 'getAuthorityData.php';
?>

View File

@ -0,0 +1,8 @@
<?php
/**
* getCorporateData.php - Wrapper für getAuthorityData.php mit authType=Corporate
*/
$_GET['authType'] = 'Corporate';
include 'getAuthorityData.php';
?>

65
ajax/getDSpaceCount.php Normal file
View File

@ -0,0 +1,65 @@
<?php
/**
* getDSpaceCount.php - Proxy für DSpace Authority-Zählung
*
* Ruft den DSpace-API-Endpoint getAuthorityCount.php auf und gibt
* die Anzahl der Datensätze zurück, die den Begriff enthalten.
*
* Parameter:
* - authType: 'Subject' oder 'Person'
* - query: Der zu suchende Begriff
*
* Response: JSON { "query": "...", "authType": "...", "count": n }
*/
header('Content-Type: application/json; charset=utf-8');
// DSpace API Basis-URL
$DSPACE_API_URL = 'https://bibb-dspace.bibb.de/DSpaceAPI/getAuthorityCount.php';
// Parameter
$authType = isset($_GET['authType']) ? trim($_GET['authType']) : '';
$query = isset($_GET['query']) ? trim($_GET['query']) : '';
// Validierung
$allowedTypes = array('Subject', 'Person');
if (!in_array($authType, $allowedTypes)) {
echo json_encode(array('error' => 'Invalid authType', 'count' => 0));
exit;
}
if (strlen($query) === 0) {
echo json_encode(array('error' => 'Missing query', 'count' => 0));
exit;
}
// DSpace API aufrufen
$url = $DSPACE_API_URL . '?authType=' . urlencode($authType) . '&query=' . urlencode($query);
$context = stream_context_create(array(
'http' => array(
'timeout' => 15,
'header' => "Accept: application/json\r\n"
),
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false
)
));
$response = @file_get_contents($url, false, $context);
if ($response === false) {
// Fallback: Count 0 zurückgeben, nicht die ganze Anzeige blockieren
echo json_encode(array(
'query' => $query,
'authType' => $authType,
'count' => -1,
'error' => 'DSpace API not reachable'
));
exit;
}
// Response direkt durchreichen
echo $response;
?>

125
ajax/getDashboardStats.php Normal file
View File

@ -0,0 +1,125 @@
<?php
/**
* getDashboardStats.php - Liefert Statistiken für das Dashboard
*/
header('Content-Type: application/json; charset=utf-8');
include "db_connection.php";
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
if (mysqli_connect_errno()) {
echo json_encode(['error' => 'Datenbankverbindung fehlgeschlagen']);
exit();
}
mysqli_set_charset($conn, "utf8");
$stats = [];
// 1. Anzahl Datensätze pro Typ
$types = ['Subject', 'Person', 'Corporate', 'Publisher', 'Classification'];
$counts = [];
foreach ($types as $type) {
$sql = "SELECT COUNT(*) as cnt FROM Anchor WHERE Type = '$type'";
$res = mysqli_query($conn, $sql);
if ($res) {
$row = mysqli_fetch_assoc($res);
$counts[$type] = intval($row['cnt']);
} else {
$counts[$type] = 0;
}
}
$stats['counts'] = $counts;
$stats['total'] = array_sum($counts);
// 2. Anzahl Relationen
$sql = "SELECT COUNT(*) as cnt FROM Linking";
$res = mysqli_query($conn, $sql);
if ($res) {
$row = mysqli_fetch_assoc($res);
$stats['relationsTotal'] = intval($row['cnt']);
} else {
$stats['relationsTotal'] = 0;
}
// 3. Zuletzt geänderte Einträge (Top 10)
$sql = "SELECT a.ID, a.Text, a.Type, e.DateModified
FROM Anchor a
JOIN Entry e ON a.Text = e.Text
WHERE e.DateModified > '2000-01-01'
ORDER BY e.DateModified DESC
LIMIT 10";
$res = mysqli_query($conn, $sql);
$recentlyModified = [];
if ($res) {
while ($row = mysqli_fetch_assoc($res)) {
$recentlyModified[] = [
'id' => $row['ID'],
'text' => $row['Text'],
'type' => $row['Type'],
'date' => $row['DateModified']
];
}
}
$stats['recentlyModified'] = $recentlyModified;
// 4. Zuletzt hinzugefügte Einträge (Top 10)
$sql = "SELECT a.ID, a.Text, a.Type, e.DateCreated
FROM Anchor a
JOIN Entry e ON a.Text = e.Text
WHERE e.DateCreated > '2000-01-01'
ORDER BY e.DateCreated DESC
LIMIT 10";
$res = mysqli_query($conn, $sql);
$recentlyCreated = [];
if ($res) {
while ($row = mysqli_fetch_assoc($res)) {
$recentlyCreated[] = [
'id' => $row['ID'],
'text' => $row['Text'],
'type' => $row['Type'],
'date' => $row['DateCreated']
];
}
}
$stats['recentlyCreated'] = $recentlyCreated;
// 5. Qualitäts-Check: Einträge ohne Relationen (Waisen)
$orphanCounts = [];
foreach ($types as $type) {
$sql = "SELECT COUNT(*) as cnt FROM Anchor a
WHERE a.Type = '$type'
AND a.ID NOT IN (SELECT DISTINCT IDAnchor FROM Linking)";
$res = mysqli_query($conn, $sql);
if ($res) {
$row = mysqli_fetch_assoc($res);
$orphanCounts[$type] = intval($row['cnt']);
} else {
$orphanCounts[$type] = 0;
}
}
$stats['orphans'] = $orphanCounts;
$stats['orphansTotal'] = array_sum($orphanCounts);
// 6. Deskriptoren vs. Non-Deskriptoren (für Schlagworte)
$sql = "SELECT
SUM(CASE WHEN DetailType = 'Deskriptor' OR DetailType = 'deskriptor' THEN 1 ELSE 0 END) as descriptors,
SUM(CASE WHEN DetailType != 'Deskriptor' AND DetailType != 'deskriptor' THEN 1 ELSE 0 END) as nonDescriptors
FROM Anchor WHERE Type = 'Subject'";
$res = mysqli_query($conn, $sql);
if ($res) {
$row = mysqli_fetch_assoc($res);
$stats['descriptors'] = intval($row['descriptors']);
$stats['nonDescriptors'] = intval($row['nonDescriptors']);
} else {
$stats['descriptors'] = 0;
$stats['nonDescriptors'] = 0;
}
mysqli_close($conn);
echo json_encode($stats, JSON_UNESCAPED_UNICODE);
?>

View File

@ -0,0 +1,122 @@
<?php
/**
* getDetailAuthorityData.php - Liefert vollständige Details zu einem Eintrag
*
* Parameter:
* - id: ID des Eintrags (kleingeschrieben von JS)
* - ID: ID des Eintrags (alternativ)
* - authType: Typ (optional)
*/
header('Content-Type: application/json; charset=utf-8');
include "db_connection.php";
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
if (mysqli_connect_errno()) {
echo json_encode(['rows' => []]);
exit();
}
mysqli_set_charset($conn, "utf8");
// Beide Parameter-Namen unterstützen (JS sendet 'id')
$id = isset($_GET['id']) ? intval($_GET['id']) : 0;
if ($id === 0) {
$id = isset($_GET['ID']) ? intval($_GET['ID']) : 0;
}
if ($id === 0) {
echo json_encode(['rows' => []]);
exit();
}
// Hauptdaten laden
$sql = "SELECT a.ID, a.Type, a.DetailType, a.Classification, a.Text as AnchorText,
e.Text, e.CompleteText, e.Comment, e.DateCreated, e.DateModified
FROM Anchor a
LEFT JOIN Entry e ON a.ID = e.ID
WHERE a.ID = $id";
$result = mysqli_query($conn, $sql);
if (!$result || mysqli_num_rows($result) === 0) {
echo json_encode(['rows' => []]);
mysqli_close($conn);
exit();
}
$data = mysqli_fetch_assoc($result);
// Relationen laden im Format das JS erwartet
$relSql = "SELECT l.ID as LinkingID, l.IDAnchor, l.IDEntry, l.Relationtype,
e.Text as EntryText, a.Type as EntryType
FROM Linking l
LEFT JOIN Entry e ON l.IDEntry = e.ID
LEFT JOIN Anchor a ON l.IDEntry = a.ID
WHERE l.IDAnchor = $id
ORDER BY l.Relationtype, e.Text";
$relResult = mysqli_query($conn, $relSql);
$relations = [];
if ($relResult) {
while ($relRow = mysqli_fetch_assoc($relResult)) {
$relations[] = [
'Relationtype' => $relRow['Relationtype'],
'TextRelation' => $relRow['EntryText'] ?? '',
'IDRelation' => $relRow['IDEntry']
];
}
}
// Synonyme laden (nur für Subject)
$synonymsText = '';
if ($data['Type'] === 'Subject' && !empty($data['Text'])) {
$desc = strtolower($data['Text']);
$desc = str_replace(' ', '_', $desc);
$synSql = "SELECT IDLinking FROM Synonyms WHERE Descriptor = '" . mysqli_real_escape_string($conn, $desc) . "' LIMIT 1";
$synResult = mysqli_query($conn, $synSql);
if ($synResult && mysqli_num_rows($synResult) > 0) {
$synRow = mysqli_fetch_assoc($synResult);
$idLinking = $synRow['IDLinking'];
$synAllSql = "SELECT Text FROM Synonyms WHERE IDLinking = $idLinking AND Descriptor != '" . mysqli_real_escape_string($conn, $desc) . "' ORDER BY Text";
$synAllResult = mysqli_query($conn, $synAllSql);
$synTexts = [];
if ($synAllResult) {
while ($syn = mysqli_fetch_assoc($synAllResult)) {
$synTexts[] = $syn['Text'];
}
}
$synonymsText = implode(', ', $synTexts);
}
}
// Descriptor erstellen
$descriptor = strtolower($data['Text'] ?? '');
$descriptor = str_replace(' ', '_', $descriptor);
mysqli_close($conn);
// Rückgabe im Format das JavaScript erwartet
echo json_encode([
'rows' => [[
'ID' => $data['ID'],
'Type' => $data['Type'],
'Detailtype' => $data['DetailType'] ?? '',
'Classification' => $data['Classification'] ?? '',
'Text' => $data['Text'] ?? '',
'CompleteText' => $data['CompleteText'] ?? '',
'Scopenote' => $data['Comment'] ?? '',
'Descriptor' => $descriptor,
'Synonyms' => $synonymsText,
'Relations' => $relations,
'DateCreated' => $data['DateCreated'] ?? '',
'DateModified' => $data['DateModified'] ?? ''
]]
], JSON_UNESCAPED_UNICODE);
?>

8
ajax/getPersonData.php Normal file
View File

@ -0,0 +1,8 @@
<?php
/**
* getPersonData.php - Wrapper für getAuthorityData.php mit authType=Person
*/
$_GET['authType'] = 'Person';
include 'getAuthorityData.php';
?>

View File

@ -0,0 +1,8 @@
<?php
/**
* getPublisherData.php - Wrapper für getAuthorityData.php mit authType=Publisher
*/
$_GET['authType'] = 'Publisher';
include 'getAuthorityData.php';
?>

104
ajax/getRelations.php Normal file
View File

@ -0,0 +1,104 @@
<?php
/**
* getRelations.php - Lädt Relationen für einen Eintrag und gibt HTML zurück
*
* Parameter:
* - anchorID: ID des Eintrags (JavaScript-Format)
* - IDAnchor: ID des Eintrags (alternatives Format)
* - authType: Typ des Eintrags
*/
header('Content-Type: text/html; charset=utf-8');
include "db_connection.php";
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
if (mysqli_connect_errno()) {
echo '';
exit();
}
mysqli_set_charset($conn, "utf8");
// Beide Parameter-Namen unterstützen (JavaScript sendet anchorID)
$idAnchor = isset($_GET['anchorID']) ? intval($_GET['anchorID']) : 0;
if ($idAnchor === 0) {
$idAnchor = isset($_GET['IDAnchor']) ? intval($_GET['IDAnchor']) : 0;
}
if ($idAnchor === 0) {
echo ''; // Leere Rückgabe bei ID 0 (neuer Eintrag)
exit();
}
// Relationen mit verknüpften Entry-Daten laden
// NUR Relationen mit einem echten Relationstyp (keine leeren, keine Selbstreferenzen)
$sql = "SELECT l.ID as LinkingID, l.IDAnchor, l.IDEntry, l.Relationtype,
e.Text as EntryText, e.CompleteText, a.Type as EntryType
FROM Linking l
LEFT JOIN Entry e ON l.IDEntry = e.ID
LEFT JOIN Anchor a ON l.IDEntry = a.ID
WHERE l.IDAnchor = $idAnchor
AND l.Relationtype != ''
AND l.IDAnchor != l.IDEntry
ORDER BY l.Relationtype, e.Text";
$result = mysqli_query($conn, $sql);
// Prüfen ob Relationen vorhanden
if (!$result || mysqli_num_rows($result) === 0) {
echo ''; // Leere Rückgabe - Bereich wird ausgeblendet
mysqli_close($conn);
exit();
}
// HTML-Tabellenzeilen generieren (analog zu Synonymen)
$html = '';
while ($row = mysqli_fetch_assoc($result)) {
$relType = htmlspecialchars($row['Relationtype']);
$entryText = htmlspecialchars($row['EntryText'] ?? '');
$linkingId = intval($row['LinkingID']);
$entryId = intval($row['IDEntry']);
// Badge-Farbe je nach Relationstyp
$badgeClass = 'bg-secondary';
$badgeTitle = $relType;
switch ($relType) {
case 'BT':
$badgeClass = 'bg-success';
$badgeTitle = 'BT - Oberbegriff';
break;
case 'NT':
$badgeClass = 'bg-danger';
$badgeTitle = 'NT - Unterbegriff';
break;
case 'RT':
$badgeClass = 'bg-warning text-dark';
$badgeTitle = 'RT - Verwandter Begriff';
break;
case 'USE':
$badgeClass = 'bg-info';
$badgeTitle = 'USE - Benutze';
break;
case 'UF':
$badgeClass = 'bg-secondary';
$badgeTitle = 'UF - Benutzt für';
break;
}
$html .= '<tr>';
$html .= '<td><span class="badge ' . $badgeClass . '" title="' . $badgeTitle . '">' . $relType . '</span></td>';
$html .= '<td>' . $entryText . '</td>';
$html .= '<td class="text-end">';
$html .= '<button class="btn btn-sm btn-outline-danger" onclick="DeleteRelation(' . $idAnchor . ', ' . $linkingId . ')" title="Relation löschen">';
$html .= '<i class="fas fa-trash"></i>';
$html .= '</button>';
$html .= '</td>';
$html .= '</tr>';
}
mysqli_close($conn);
echo $html;
?>

8
ajax/getSubjectData.php Normal file
View File

@ -0,0 +1,8 @@
<?php
/**
* getSubjectData.php - Wrapper für getAuthorityData.php mit authType=Subject
*/
$_GET['authType'] = 'Subject';
include 'getAuthorityData.php';
?>

82
ajax/getSynonyms.php Normal file
View File

@ -0,0 +1,82 @@
<?php
/**
* getSynonyms.php - Lädt Synonyme für einen Begriff
*
* Parameter:
* - text: Der Begriff, für den Synonyme gesucht werden
*/
header('Content-Type: application/json; charset=utf-8');
include "db_connection.php";
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
if (mysqli_connect_errno()) {
echo json_encode(['success' => false, 'error' => 'Datenbankverbindung fehlgeschlagen']);
exit();
}
mysqli_set_charset($conn, "utf8");
$text = isset($_GET['text']) ? trim($_GET['text']) : '';
if (empty($text)) {
echo json_encode(['success' => true, 'synonyms' => [], 'html' => '']);
exit();
}
// Descriptor erstellen (Kleinbuchstaben, Leerzeichen durch Unterstrich)
function prepare_desc($text) {
$desc = strtolower($text);
$desc = str_replace(' ', '_', $desc);
return $desc;
}
$desc = prepare_desc($text);
// Erst IDLinking für den Begriff finden
$sql = "SELECT IDLinking FROM Synonyms WHERE Descriptor = '" . mysqli_real_escape_string($conn, $desc) . "' LIMIT 1";
$res = mysqli_query($conn, $sql);
$synonyms = [];
$html = '';
if ($res && mysqli_num_rows($res) > 0) {
$row = mysqli_fetch_assoc($res);
$idLinking = $row['IDLinking'];
// Alle Synonyme mit dieser IDLinking laden (außer dem Begriff selbst)
$sql2 = "SELECT ID, Text, Descriptor FROM Synonyms WHERE IDLinking = $idLinking AND Descriptor != '" . mysqli_real_escape_string($conn, $desc) . "' ORDER BY Text";
$res2 = mysqli_query($conn, $sql2);
if ($res2) {
while ($syn = mysqli_fetch_assoc($res2)) {
$synonyms[] = [
'id' => $syn['ID'],
'text' => $syn['Text'],
'descriptor' => $syn['Descriptor']
];
// HTML für Anzeige generieren
$html .= '<tr>';
$html .= '<td><span class="badge" style="background-color: #e91e63;">SYN</span></td>';
$html .= '<td>' . htmlspecialchars($syn['Text']) . '</td>';
$html .= '<td class="text-end">';
$html .= '<button class="btn btn-sm btn-outline-danger" onclick="DeleteSynonym(\'' . addslashes($text) . '\', \'' . addslashes($syn['Text']) . '\')" title="Synonym entfernen">';
$html .= '<i class="fas fa-trash"></i>';
$html .= '</button>';
$html .= '</td>';
$html .= '</tr>';
}
}
}
echo json_encode([
'success' => true,
'synonyms' => $synonyms,
'html' => $html,
'count' => count($synonyms)
]);
mysqli_close($conn);
?>

75
ajax/getTreeData.php Normal file
View File

@ -0,0 +1,75 @@
<?php
/**
* getTreeData.php - Liefert Klassifikationsdaten im jsTree-Format
*
* Nutzt die bestehende Treeview-Tabelle
*
* Parameter:
* - id: Parent-ID (# oder 0 für Root-Ebene)
*
* Rückgabe: JSON-Array im jsTree-Format
*/
header('Content-Type: application/json; charset=utf-8');
// Datenbankverbindung
include "db_connection.php";
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
if (mysqli_connect_errno()) {
echo json_encode([]);
exit();
}
mysqli_set_charset($conn, "utf8");
// Parameter auslesen
$parentId = isset($_GET['id']) ? $_GET['id'] : '#';
// Root-Ebene: parent_id ist NULL oder 0
if ($parentId === '#' || $parentId === '0' || $parentId === 0) {
$sql = "SELECT id, name, text, link as href, parent_id
FROM Treeview
WHERE parent_id IS NULL OR parent_id = 0 OR parent_id = ''
ORDER BY text";
} else {
// Unterebene: Kinder des angegebenen Parents
$parentId = intval($parentId);
$sql = "SELECT id, name, text, link as href, parent_id
FROM Treeview
WHERE parent_id = $parentId
ORDER BY text";
}
$res = mysqli_query($conn, $sql);
if (!$res) {
echo json_encode([]);
exit();
}
$result = [];
while ($row = mysqli_fetch_assoc($res)) {
// Prüfen ob dieser Knoten Kinder hat
$childCheckSql = "SELECT COUNT(*) as cnt FROM Treeview WHERE parent_id = " . intval($row['id']);
$childRes = mysqli_query($conn, $childCheckSql);
$childRow = mysqli_fetch_assoc($childRes);
$hasChildren = ($childRow['cnt'] > 0);
$result[] = [
'id' => strval($row['id']),
'text' => $row['text'] ? $row['text'] : $row['name'],
'children' => $hasChildren,
'data' => [
'name' => $row['name'],
'href' => $row['href']
],
'icon' => $hasChildren ? 'fas fa-folder text-warning' : 'fas fa-file text-secondary'
];
}
mysqli_close($conn);
echo json_encode($result, JSON_UNESCAPED_UNICODE);
?>

46
ajax/getTreePath.php Normal file
View File

@ -0,0 +1,46 @@
<?php
/**
* getTreePath.php - Liefert den Pfad von einem Knoten bis zur Wurzel
*
* Parameter (GET):
* - id: Treeview-ID des Zielknotens
*
* Rückgabe: JSON-Array mit IDs von Root bis Zielknoten (inklusive)
* Beispiel: [98106, 98107, 98113, 98115]
*/
header('Content-Type: application/json; charset=utf-8');
include "db_connection.php";
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
if (mysqli_connect_errno()) {
echo json_encode([]);
exit();
}
mysqli_set_charset($conn, "utf8");
$targetId = isset($_GET['id']) ? intval($_GET['id']) : 0;
if ($targetId === 0) {
echo json_encode([]);
exit();
}
// Pfad aufbauen: vom Zielknoten nach oben bis Root (parent_id = 0 oder leer)
$path = [];
$curId = $targetId;
$limit = 20; // Schutz vor Endlosschleife bei defekten Daten
while ($curId > 0 && $limit-- > 0) {
$res = mysqli_query($conn, "SELECT id, parent_id FROM Treeview WHERE id = $curId LIMIT 1");
if (!$res) break;
$row = mysqli_fetch_assoc($res);
if (!$row) break;
array_unshift($path, intval($row['id'])); // vorne einfügen → Root zuerst
$parentId = intval($row['parent_id']);
if ($parentId === 0) break;
$curId = $parentId;
}
mysqli_close($conn);
echo json_encode($path, JSON_UNESCAPED_UNICODE);
?>

561
ajax/libAuthorities.php Normal file
View File

@ -0,0 +1,561 @@
<?php
/**
* libAuthorities.php - CRUD-Klasse für Thesaurus-Datenbank
*
* Sicherheitsverbesserungen:
* - Prepared Statements mit benannten Parametern
* - Whitelist für authType
* - Input-Validierung
* - Fehlerbehandlung
*/
class CRUD
{
protected $db;
// Whitelist für erlaubte Typen
private $allowedTypes = ['Subject', 'Person', 'Corporate', 'Publisher', 'Classification'];
// Whitelist für erlaubte Sortierfelder
private $allowedSortFields = [
'Anchor.ID', 'Anchor.Text', 'Anchor.Type', 'Anchor.DetailType',
'Entry.Text', 'Entry.ID', 'Entry.DateCreated', 'Entry.DateModified'
];
function __construct()
{
$this->db = DB();
}
function __destruct()
{
$this->db = null;
}
/**
* Validiert den authType gegen Whitelist
*/
private function validateAuthType($authType)
{
if (!in_array($authType, $this->allowedTypes)) {
throw new InvalidArgumentException("Ungültiger Typ: $authType");
}
return $authType;
}
/**
* Validiert und bereinigt Sortierfeld
*/
private function validateSortField($sort)
{
if (empty($sort)) {
return 'Entry.Text';
}
$sortField = trim(str_replace(['ASC', 'DESC', 'asc', 'desc'], '', $sort));
$sortField = trim($sortField);
if (!in_array($sortField, $this->allowedSortFields)) {
return 'Entry.Text';
}
return $sort;
}
/**
* Validiert Integer-Werte
*/
private function validateInt($value, $min = 0)
{
$intVal = intval($value);
return ($intVal >= $min) ? $intVal : $min;
}
/**
* Baut ein SQL-LIKE-Pattern aus einem Suchterm.
* Führendes * Linkstrunkierung (%...)
* Abschließendes * Rechtstrunkierung (...%)
* Kein * nur Rechtstrunkierung (Standard-Verhalten)
*/
private function buildSearchPattern(string $term): string
{
$leftTrunc = str_starts_with($term, '*');
$rightTrunc = str_ends_with($term, '*');
$clean = trim($term, '*');
$clean = addcslashes($clean, '%_\\');
return ($leftTrunc ? '%' : '') .
$clean .
($rightTrunc ? '%' : '%');
}
/**
* Dubletten-Prüfung über Anchor.Text (normalisierte Form).
*
* Anchor.Text enthält den normalisierten Descriptor (Kleinbuchstaben,
* Sonderzeichen durch '_' ersetzt, erzeugt durch prepare_desc()).
* Diese Prüfung ist robuster als ein Vergleich auf Entry.Text, weil
* sie Schreibvarianten (Groß-/Kleinschreibung, Umlaute, Leerzeichen)
* zuverlässig als Dublette erkennt.
*
* @param string $authType Typ-Whitelist-Wert ('Subject', 'Person', ...)
* @param string $normalizedText Ergebnis von prepare_desc($term)
* @return int Anzahl gefundener Einträge (0 = keine Dublette)
*/
public function checkDuplicateByNormalizedText($authType, $normalizedText)
{
$authType = $this->validateAuthType($authType);
$stmt = $this->db->prepare(
"SELECT COUNT(*) AS count
FROM Anchor
WHERE Text = :normalizedText
AND Type = :authType"
);
$stmt->execute([
':normalizedText' => $normalizedText,
':authType' => $authType
]);
$rec = $stmt->fetch(PDO::FETCH_ASSOC);
return $rec ? intval($rec['count']) : 0;
}
public function readAuthorityNumbersOfRecords($authType, $search, $id, $sharp)
{
error_log("DEBUG search='" . $search . "' pattern='" . $this->buildSearchPattern($search) . "'");
$authType = $this->validateAuthType($authType);
$id = $this->validateInt($id);
$params = [':authType' => $authType];
$query = "SELECT COUNT(*) as count FROM Anchor
JOIN Entry ON (Anchor.ID = Entry.ID)
WHERE Type = :authType";
if (strlen($search) > 0 && $sharp == false) {
$query .= " AND LOWER(Entry.CompleteText) LIKE LOWER(:search)";
$params[':search'] = $this->buildSearchPattern($search);
}
if (strlen($search) > 0 && $sharp == true) {
$query .= " AND LOWER(Entry.Text) = LOWER(:search)";
$params[':search'] = $search;
}
if ($id > 0) {
$query .= " AND Entry.ID = :id";
$params[':id'] = $id;
}
$stmt = $this->db->prepare($query);
$stmt->execute($params);
$rec = $stmt->fetch(PDO::FETCH_ASSOC);
return $rec ? intval($rec['count']) : 0;
}
public function readAuthorityEntriesByID($authType, $id)
{
$authType = $this->validateAuthType($authType);
$id = $this->validateInt($id, 1);
$query = "SELECT Anchor.ID AS ID, Anchor.DetailType AS DetailType,
Anchor.Type AS Type, Anchor.Classification AS Classification,
Entry.Text as Text, Entry.Comment as Scopenote
FROM Anchor
JOIN Entry ON (Anchor.ID = Entry.ID)
WHERE Anchor.ID = :id AND Type = :authType";
$stmt = $this->db->prepare($query);
$stmt->execute([':id' => $id, ':authType' => $authType]);
$count = $stmt->rowCount();
$ret = [];
while ($rec = $stmt->fetch(PDO::FETCH_ASSOC)) {
$relations = $this->getRelations($authType, $rec['ID']);
$ret[] = [
"ID" => $rec['ID'],
"Descriptor" => $relations['Descriptor'],
"Text" => $rec['Text'],
"DetailType" => $rec['DetailType'],
"Type" => $rec['Type'],
"Scopenote" => $rec['Scopenote'],
"Relations" => $relations['Data'],
"Classification" => $rec['Classification']
];
}
return ["COUNT" => $count, "RECORDS" => $ret];
}
public function readAuthorityEntries($authType, $offset, $search, $id, $sort, $max)
{
error_log("DEBUG search='" . $search . "' pattern='" . $this->buildSearchPattern($search) . "'", 3, "error.log");
$authType = $this->validateAuthType($authType);
$offset = $this->validateInt($offset);
$max = $this->validateInt($max, 1);
$id = $this->validateInt($id);
$sort = $this->validateSortField($sort);
$params = [':authType' => $authType];
$query = "SELECT Anchor.ID AS ID, Anchor.DetailType AS DetailType,
Anchor.Type AS Type, Anchor.Classification AS Classification,
Entry.Text as Text, Entry.Comment as Scopenote
FROM Anchor
JOIN Entry ON (Anchor.ID = Entry.ID)
WHERE Type = :authType";
if (strlen($search) > 0) {
$query .= " AND LOWER(Entry.CompleteText) LIKE LOWER(:search)";
$params[':search'] = $this->buildSearchPattern($search);
}
if ($id > 0) {
$query .= " AND Entry.ID = :id";
$params[':id'] = $id;
}
// $query .= " ORDER BY " . $sort . " LIMIT " . intval($offset) . "," . intval($max);
if ($authType === 'Classification') {
$orderClause = "
SUBSTRING_INDEX(Anchor.Text, ' ', 1),
CAST(
CASE
WHEN LOCATE(' ', Anchor.Text) > 0
THEN SUBSTRING_INDEX(SUBSTRING_INDEX(Anchor.Text, ' ', -1), '.', 1)
ELSE NULL
END AS UNSIGNED
),
CAST(
CASE
WHEN LOCATE('.', Anchor.Text) > 0
THEN SUBSTRING_INDEX(Anchor.Text, '.', -1)
ELSE NULL
END AS UNSIGNED
)
";
} else {
$orderClause = $sort . " COLLATE utf8mb4_unicode_ci";
}
error_log("DEBUG ORDER BY: " . $orderClause, 3, "/var/www/tools/html/Thesaurus/error.log");
$query .= " ORDER BY " . $orderClause . " LIMIT " . intval($offset) . "," . intval($max);
$stmt = $this->db->prepare($query);
$stmt->execute($params);
$count = $stmt->rowCount();
$ret = [];
while ($rec = $stmt->fetch(PDO::FETCH_ASSOC)) {
$relations = $this->getRelations($authType, $rec['ID']);
if ($authType === 'Subject') {
$synonyms = $this->getSynonyms($rec['Text']);
$ret[] = [
"ID" => $rec['ID'],
"Descriptor" => $relations['Descriptor'],
"Text" => $rec['Text'],
"DetailType" => $rec['DetailType'],
"Type" => $rec['Type'],
"Scopenote" => $rec['Scopenote'],
"Relations" => $relations['Data'],
"Classification" => $rec['Classification'],
"Synonyms" => $synonyms['Synonyms'],
"Search" => $synonyms['Search']
];
} else {
$ret[] = [
"ID" => $rec['ID'],
"Descriptor" => $relations['Descriptor'],
"Text" => $rec['Text'],
"DetailType" => $rec['DetailType'],
"Type" => $rec['Type'],
"Scopenote" => $rec['Scopenote'],
"Relations" => $relations['Data'],
"Classification" => $rec['Classification']
];
}
}
return ["COUNT" => $count, "RECORDS" => $ret];
}
public function getRelations($authType, $id)
{
$authType = $this->validateAuthType($authType);
$id = $this->validateInt($id, 1);
$query = "SELECT Linking.ID as LinkingID, IDEntry, Entry.Text, Entry.Comment,
Relationtype, DetailType
FROM Linking
JOIN Entry ON (Linking.IDEntry = Entry.ID)
JOIN Anchor ON (IDAnchor = Anchor.ID)
WHERE IDAnchor = :id AND Anchor.Type = :authType AND Relationtype != ''
ORDER BY Relationtype";
$stmt = $this->db->prepare($query);
$stmt->execute([':id' => $id, ':authType' => $authType]);
$descriptor = true;
$ret = [];
while ($rec = $stmt->fetch(PDO::FETCH_ASSOC)) {
if (strtolower($rec['Relationtype']) === "use") {
$descriptor = false;
}
$ret[] = [
"IDLinking" => $rec['LinkingID'],
"IDEntry" => $rec['IDEntry'],
"IDRelation" => $rec['IDEntry'],
"Detailtype" => $rec['DetailType'],
"TextRelation" => $rec['Text'],
"CommentRelation"=> $rec['Comment'],
"Relationtype" => $rec['Relationtype']
];
}
return ["Descriptor" => $descriptor, "Data" => $ret];
}
public function getSynonyms($input)
{
$desc = $this->prepare_desc($input);
$query = "SELECT IDLinking FROM Synonyms WHERE Descriptor = :desc";
$stmt = $this->db->prepare($query);
$stmt->execute([':desc' => $desc]);
$ret = '';
$retsearch = "topic:'" . addslashes($input) . "' OR ";
while ($rec = $stmt->fetch(PDO::FETCH_ASSOC)) {
$idSearch = intval($rec['IDLinking']);
$query2 = "SELECT Text FROM Synonyms WHERE IDLinking = :idLinking";
$stmt2 = $this->db->prepare($query2);
$stmt2->execute([':idLinking' => $idSearch]);
while ($rec2 = $stmt2->fetch(PDO::FETCH_ASSOC)) {
if ($rec2['Text'] === $input) continue;
$ret .= $rec2['Text'] . ", ";
$retsearch .= "topic:'" . addslashes($rec2['Text']) . "' OR ";
}
}
if (strlen($ret) == 0) {
return ["Synonyms" => '', "Search" => ''];
} else {
$synonyms = trim(substr($ret, 0, -2));
$search = trim(substr($retsearch, 0, -4));
return ["Synonyms" => $synonyms, "Search" => $search];
}
}
public function getEntryText($authType, $request)
{
$authType = $this->validateAuthType($authType);
$query = "SELECT Anchor.ID AS AnchorID, Entry.Text as Text
FROM Anchor
JOIN Entry ON (Anchor.ID = Entry.ID)
WHERE Anchor.Text LIKE LOWER(:request) AND Anchor.Type = :authType
ORDER BY Entry.Text";
$stmt = $this->db->prepare($query);
$stmt->execute([':request' => '%' . $request . '%', ':authType' => $authType]);
$ret = [];
while ($rec = $stmt->fetch(PDO::FETCH_ASSOC)) {
$ret[] = ["Text" => $rec['Text'], "ID" => $rec['AnchorID']];
}
return $ret;
}
public function insertNewTerm($authType, $term, $desc, $detailtype, $classification, $scopenote, $completetext)
{
$authType = $this->validateAuthType($authType);
$query = $this->db->prepare(
"INSERT INTO Anchor (ID, Text, DetailType, Type, Classification)
VALUES (:ID, :Text, :DetailType, :Type, :Classification)"
);
$query->execute([
':ID' => null,
':Text' => $desc,
':DetailType' => $detailtype,
':Type' => $authType,
':Classification' => $classification
]);
$IDAnchor = $this->db->lastInsertId();
$query = $this->db->prepare(
"INSERT INTO Entry (ID, Text, Comment, Language, CompleteText, DateCreated, DateModified)
VALUES (:ID, :Text, :Comment, :Language, :CompleteText, :DateCreated, :DateModified)"
);
$query->execute([
':ID' => $IDAnchor,
':Text' => $term,
':Comment' => $scopenote,
':Language' => 'de',
':CompleteText' => $completetext,
':DateCreated' => date('Y-m-d H:i:s'),
':DateModified' => '0000-00-00 00:00:00'
]);
$IDEntry = $IDAnchor;
return ["IDAnchor" => $IDAnchor, "IDEntry" => $IDEntry, "IDLinking" => 0];
}
public function writeNewRelation($anchorID, $relationType, $relationID)
{
$anchorID = $this->validateInt($anchorID, 1);
$relationID = $this->validateInt($relationID, 1);
$allowedRelations = ['BT', 'NT', 'RT', 'USE', 'UF'];
if (!in_array(strtoupper($relationType), $allowedRelations)) {
throw new InvalidArgumentException("Ungültiger Relationstyp: $relationType");
}
$query = $this->db->prepare(
"INSERT INTO Linking (ID, IDAnchor, IDEntry, Relationtype)
VALUES (:ID, :IDAnchor, :IDEntry, :Relationtype)"
);
$query->execute([
':ID' => null,
':IDAnchor' => $anchorID,
':IDEntry' => $relationID,
':Relationtype' => $relationType
]);
return $this->db->lastInsertId();
}
public function deleteRelation($anchorID, $linkingID)
{
$linkingID = $this->validateInt($linkingID, 1);
$query = $this->db->prepare("DELETE FROM Linking WHERE ID = :id");
$query->execute([':id' => $linkingID]);
return $linkingID;
}
public function deleteTerm($anchorID)
{
$anchorID = $this->validateInt($anchorID, 1);
$ret = [];
$query = $this->db->prepare("DELETE FROM Linking WHERE IDAnchor = :id");
$query->execute([':id' => $anchorID]);
$ret[] = "Linking gelöscht";
$query = $this->db->prepare("DELETE FROM Entry WHERE ID = :id");
$query->execute([':id' => $anchorID]);
$ret[] = "Entry gelöscht";
$query = $this->db->prepare("DELETE FROM Anchor WHERE ID = :id");
$query->execute([':id' => $anchorID]);
$ret[] = "Anchor gelöscht";
return $ret;
}
public function updateTerm($anchorID, $authType, $desc, $term, $type, $detailtype, $classification, $scopenote, $completetext)
{
$anchorID = $this->validateInt($anchorID, 1);
$authType = $this->validateAuthType($authType);
$query = $this->db->prepare(
"UPDATE Anchor
SET Text = :text, Type = :type, DetailType = :detailtype, Classification = :classification
WHERE ID = :id"
);
$query->execute([
':text' => $desc,
':type' => $authType,
':detailtype' => $detailtype,
':classification' => $classification,
':id' => $anchorID
]);
$query = $this->db->prepare(
"UPDATE Entry
SET Text = :text, Comment = :comment, Language = :language,
CompleteText = :completetext, DateModified = :datemodified
WHERE ID = :id"
);
$query->execute([
':text' => $term,
':comment' => $scopenote,
':language' => 'de',
':completetext' => $completetext,
':datemodified' => date('Y-m-d H:i:s'),
':id' => $anchorID
]);
return "OK";
}
public function getStatistics($dateStart, $dateEnd)
{
if (!preg_match('/^\d{4}-\d{2}-\d{2}/', $dateStart) ||
!preg_match('/^\d{4}-\d{2}-\d{2}/', $dateEnd)) {
throw new InvalidArgumentException("Ungültiges Datumsformat");
}
$r = [];
$dateTypes = ["DateCreated", "DateModified"];
foreach ($this->allowedTypes as $type) {
foreach ($dateTypes as $dateType) {
$query = $this->db->prepare(
"SELECT COUNT(*) AS COUNT
FROM Anchor
JOIN Entry ON (Anchor.ID = Entry.ID)
WHERE Type = :type AND $dateType BETWEEN :dateStart AND :dateEnd"
);
$query->execute([
':type' => $type,
':dateStart' => $dateStart,
':dateEnd' => $dateEnd
]);
$rec = $query->fetch(PDO::FETCH_ASSOC);
$r[$type][$dateType] = $rec ? intval($rec['COUNT']) : 0;
}
}
return $r;
}
public function prepare_desc($text)
{
$desc = is_array($text) ? $text[0] : $text;
$text = strtolower($desc);
$desc = str_replace(' ', '_', $text);
$search = ['ü', 'ä', 'ö', 'ß', '.', ',', 'Ö', 'Ü', 'Ä', '[', ']', '<', '>', '""'];
$replace = ['ue', 'ae', 'oe', 'ss', '', '', 'oe', 'ue', 'ae', '_', '_', '&lt;', '&gt;', '"'];
$desc = str_replace($search, $replace, $desc);
return $desc;
}
}
?>

134
ajax/newClassification.php Normal file
View File

@ -0,0 +1,134 @@
<?php
/**
* newClassification.php - Erstellt einen neuen Klassifikationseintrag
*
* Nutzt die CRUD-Klasse aus libAuthorities.php.
* Nach dem Anlegen in Anchor/Entry wird automatisch ein Eintrag
* in der Treeview-Tabelle erstellt und an der korrekten Position
* im Baum eingehängt.
*/
header('Content-Type: application/json; charset=utf-8');
include "db_connection.php";
include "libAuthorities.php";
// Parameter aus JavaScript
$term = isset($_POST['term']) ? trim($_POST['term']) : '';
$notation = isset($_POST['notation']) ? trim($_POST['notation']) : '';
$detailType = isset($_POST['detailtype']) ? trim($_POST['detailtype']) : '';
$classification = isset($_POST['classification']) ? trim($_POST['classification']) : '';
$scopenote = isset($_POST['scopenote']) ? trim($_POST['scopenote']) : '';
// Notation hat Vorrang vor Classification
if (!empty($notation)) {
$classification = $notation;
}
if (empty($term)) {
echo json_encode(['status' => 400, 'message' => 'Bitte Text eingeben!']);
exit();
}
/**
* Ermittelt die Eltern-Notation durch Abschneiden des letzten Segments.
*
* Beispiele:
* "T 4.10" "T 4"
* "G 1.1.1" "G 1.1"
* "G 1.1" "G 1"
* "G 1" "G"
* "G" null (Root-Ebene)
*/
function getParentNotation($notation) {
$lastDot = strrpos($notation, '.');
if ($lastDot !== false) {
return substr($notation, 0, $lastDot);
}
$lastSpace = strrpos($notation, ' ');
if ($lastSpace !== false) {
return substr($notation, 0, $lastSpace);
}
return null; // Root
}
/**
* Sucht die Treeview-ID für eine gegebene Notation.
* Die Notation steht am Anfang des name-Feldes, gefolgt von einem Leerzeichen
* oder ist identisch mit dem name-Feld (bei Blatt-Knoten ohne Titel).
*
* @param mysqli $conn
* @param string $notation z.B. "T 4"
* @return int Treeview-ID oder 0 wenn nicht gefunden
*/
function findTreeviewIdByNotation($conn, $notation) {
$escaped = mysqli_real_escape_string($conn, $notation);
// Entweder exakter Match oder Notation + Leerzeichen am Anfang
$sql = "SELECT id FROM Treeview
WHERE name = '$escaped'
OR name LIKE '$escaped %'
ORDER BY id
LIMIT 1";
$res = mysqli_query($conn, $sql);
if ($res) {
$row = mysqli_fetch_assoc($res);
if ($row) return intval($row['id']);
}
return 0;
}
try {
$crud = new CRUD();
// Normalisierten Descriptor erzeugen
$desc = $crud->prepare_desc($term);
$completetext = $term;
// Dubletten-Prüfung (normalisiert)
$existing = $crud->checkDuplicateByNormalizedText('Classification', $desc);
if ($existing > 0) {
echo json_encode(['status' => 409, 'message' => 'Eine Klassifikation mit diesem Text existiert bereits']);
exit();
}
// Neuen Eintrag in Anchor + Entry erstellen
$result = $crud->insertNewTerm('Classification', $term, $desc, $detailType, $classification, $scopenote, $completetext);
// --- Treeview-Eintrag anlegen ---
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
if (!mysqli_connect_errno()) {
mysqli_set_charset($conn, "utf8");
// Parent-ID ermitteln
$parentId = 0; // Default: Root
if (!empty($classification)) {
$parentNotation = getParentNotation($classification);
if ($parentNotation !== null) {
$foundId = findTreeviewIdByNotation($conn, $parentNotation);
if ($foundId > 0) {
$parentId = $foundId;
}
// Hinweis: wenn Parent nicht gefunden → landet im Root (parent_id = 0)
}
}
// name und text = vollständiger Term (Notation + Bezeichnung), link = '#'
$nameEscaped = mysqli_real_escape_string($conn, $term);
$insertSql = "INSERT INTO Treeview (name, text, link, parent_id)
VALUES ('$nameEscaped', '$nameEscaped', '#', '$parentId')";
mysqli_query($conn, $insertSql);
$treeviewId = mysqli_insert_id($conn);
mysqli_close($conn);
}
echo json_encode([
'status' => 200,
'message' => 'Klassifikation erfolgreich erstellt',
'Anchor' => $result['IDAnchor'],
'Entry' => $result['IDEntry'],
'Linking' => $result['IDLinking'],
'TreeviewID' => $treeviewId ?? 0
]);
} catch (Exception $e) {
echo json_encode(['status' => 500, 'message' => $e->getMessage()]);
}
?>

52
ajax/newCorporate.php Normal file
View File

@ -0,0 +1,52 @@
<?php
/**
* newCorporate.php - Erstellt einen neuen Körperschaftseintrag
*
* Nutzt die CRUD-Klasse aus libAuthorities.php
*/
header('Content-Type: application/json; charset=utf-8');
include "db_connection.php";
include "libAuthorities.php";
// Parameter aus JavaScript
$term = isset($_POST['term']) ? trim($_POST['term']) : '';
$detailType = isset($_POST['detailtype']) ? trim($_POST['detailtype']) : '';
$classification = isset($_POST['classification']) ? trim($_POST['classification']) : '';
$scopenote = isset($_POST['scopenote']) ? trim($_POST['scopenote']) : '';
if (empty($term)) {
echo json_encode(['status' => 400, 'message' => 'Bitte Namen eingeben!']);
exit();
}
try {
$crud = new CRUD();
// Descriptor erstellen (für Anchor.Text)
$desc = $crud->prepare_desc($term);
$completetext = $term;
// Prüfen ob bereits existiert
$existing = $crud->readAuthorityNumbersOfRecords('Corporate', $term, 0, true);
if ($existing > 0) {
echo json_encode(['status' => 409, 'message' => 'Eine Körperschaft mit diesem Namen existiert bereits']);
exit();
}
// Neuen Eintrag erstellen
$result = $crud->insertNewTerm('Corporate', $term, $desc, $detailType, $classification, $scopenote, $completetext);
echo json_encode([
'status' => 200,
'message' => 'Körperschaft erfolgreich erstellt',
'Anchor' => $result['IDAnchor'],
'Entry' => $result['IDEntry'],
'Linking' => $result['IDLinking']
]);
} catch (Exception $e) {
echo json_encode(['status' => 500, 'message' => $e->getMessage()]);
}
?>

51
ajax/newPerson.php Normal file
View File

@ -0,0 +1,51 @@
<?php
/**
* newPerson.php - Erstellt einen neuen Personeneintrag
*
* Nutzt die CRUD-Klasse aus libAuthorities.php
*/
header('Content-Type: application/json; charset=utf-8');
include "db_connection.php";
include "libAuthorities.php";
// Parameter aus JavaScript
$term = isset($_POST['term']) ? trim($_POST['term']) : '';
$detailType = isset($_POST['detailtype']) ? trim($_POST['detailtype']) : '';
$classification = isset($_POST['classification']) ? trim($_POST['classification']) : '';
$scopenote = isset($_POST['scopenote']) ? trim($_POST['scopenote']) : '';
if (empty($term)) {
echo json_encode(['status' => 400, 'message' => 'Bitte Namen eingeben!']);
exit();
}
try {
$crud = new CRUD();
// Normalisierten Descriptor-Text erzeugen (entspricht Anchor.Text in der DB)
$desc = $crud->prepare_desc($term);
$completetext = $term;
// Dubletten-Prüfung gegen Anchor.Text (normalisierte Form)
// Verhindert Fehlalarme durch abweichende Groß-/Kleinschreibung oder Sonderzeichen
$existing = $crud->checkDuplicateByNormalizedText('Person', $desc);
if ($existing > 0) {
echo json_encode(['status' => 409, 'message' => 'Eine Person mit diesem Namen existiert bereits']);
exit();
}
// Neuen Eintrag erstellen
$result = $crud->insertNewTerm('Person', $term, $desc, $detailType, $classification, $scopenote, $completetext);
echo json_encode([
'status' => 200,
'message' => 'Person erfolgreich erstellt',
'Anchor' => $result['IDAnchor'],
'Entry' => $result['IDEntry'],
'Linking' => $result['IDLinking']
]);
} catch (Exception $e) {
echo json_encode(['status' => 500, 'message' => $e->getMessage()]);
}
?>

52
ajax/newPublisher.php Normal file
View File

@ -0,0 +1,52 @@
<?php
/**
* newPublisher.php - Erstellt einen neuen Verlagseintrag
*
* Nutzt die CRUD-Klasse aus libAuthorities.php
*/
header('Content-Type: application/json; charset=utf-8');
include "db_connection.php";
include "libAuthorities.php";
// Parameter aus JavaScript
$term = isset($_POST['term']) ? trim($_POST['term']) : '';
$detailType = isset($_POST['detailtype']) ? trim($_POST['detailtype']) : '';
$classification = isset($_POST['classification']) ? trim($_POST['classification']) : '';
$scopenote = isset($_POST['scopenote']) ? trim($_POST['scopenote']) : '';
if (empty($term)) {
echo json_encode(['status' => 400, 'message' => 'Bitte Namen eingeben!']);
exit();
}
try {
$crud = new CRUD();
// Descriptor erstellen (für Anchor.Text)
$desc = $crud->prepare_desc($term);
$completetext = $term;
// Prüfen ob bereits existiert
$existing = $crud->readAuthorityNumbersOfRecords('Publisher', $term, 0, true);
if ($existing > 0) {
echo json_encode(['status' => 409, 'message' => 'Ein Verlag mit diesem Namen existiert bereits']);
exit();
}
// Neuen Eintrag erstellen
$result = $crud->insertNewTerm('Publisher', $term, $desc, $detailType, $classification, $scopenote, $completetext);
echo json_encode([
'status' => 200,
'message' => 'Verlag erfolgreich erstellt',
'Anchor' => $result['IDAnchor'],
'Entry' => $result['IDEntry'],
'Linking' => $result['IDLinking']
]);
} catch (Exception $e) {
echo json_encode(['status' => 500, 'message' => $e->getMessage()]);
}
?>

50
ajax/newSubject.php Normal file
View File

@ -0,0 +1,50 @@
<?php
/**
* newSubject.php - Erstellt einen neuen Schlagwort-Eintrag
*
* Nutzt die CRUD-Klasse aus libAuthorities.php
*/
header('Content-Type: application/json; charset=utf-8');
include "db_connection.php";
include "libAuthorities.php";
// Parameter aus JavaScript (SubjectScript.js → CreateNewEntry)
$term = isset($_POST['term']) ? trim($_POST['term']) : '';
$detailType = isset($_POST['detailtype']) ? trim($_POST['detailtype']) : '';
$classification = isset($_POST['classification']) ? trim($_POST['classification']) : '';
$scopenote = isset($_POST['scopenote']) ? trim($_POST['scopenote']) : '';
if (empty($term)) {
echo json_encode(['status' => 400, 'message' => 'Bitte Schlagwort eingeben!']);
exit();
}
try {
$crud = new CRUD();
// Normalisierten Descriptor-Text erzeugen (entspricht Anchor.Text in der DB)
$desc = $crud->prepare_desc($term);
$completetext = $term;
// Dubletten-Prüfung gegen Anchor.Text (normalisierte Form)
$existing = $crud->checkDuplicateByNormalizedText('Subject', $desc);
if ($existing > 0) {
echo json_encode(['status' => 409, 'message' => 'Ein Schlagwort mit diesem Begriff existiert bereits']);
exit();
}
// Neuen Eintrag erstellen
$result = $crud->insertNewTerm('Subject', $term, $desc, $detailType, $classification, $scopenote, $completetext);
echo json_encode([
'status' => 200,
'message' => 'Schlagwort erfolgreich erstellt',
'Anchor' => $result['IDAnchor'],
'Entry' => $result['IDEntry'],
'Linking' => $result['IDLinking']
]);
} catch (Exception $e) {
echo json_encode(['status' => 500, 'message' => $e->getMessage()]);
}
?>

123
ajax/saveSynonym.php Normal file
View File

@ -0,0 +1,123 @@
<?php
/**
* saveSynonym.php - Speichert ein neues Synonym
*
* Parameter:
* - text1: Erster Begriff (bereits existierend)
* - text2: Zweiter Begriff (neues Synonym)
* - action: 'add' oder 'remove'
*/
header('Content-Type: application/json; charset=utf-8');
include "db_connection.php";
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
if (mysqli_connect_errno()) {
echo json_encode(['success' => false, 'error' => 'Datenbankverbindung fehlgeschlagen']);
exit();
}
mysqli_set_charset($conn, "utf8");
$text1 = isset($_POST['text1']) ? trim($_POST['text1']) : '';
$text2 = isset($_POST['text2']) ? trim($_POST['text2']) : '';
$action = isset($_POST['action']) ? $_POST['action'] : 'add';
if (empty($text1) || empty($text2)) {
echo json_encode(['success' => false, 'error' => 'Beide Begriffe müssen angegeben werden']);
exit();
}
// Descriptor erstellen (Kleinbuchstaben, Leerzeichen durch Unterstrich)
function prepare_desc($text) {
$desc = strtolower($text);
$desc = str_replace(' ', '_', $desc);
return $desc;
}
$desc1 = prepare_desc($text1);
$desc2 = prepare_desc($text2);
if ($action === 'add') {
// Prüfen ob text1 bereits eine IDLinking hat
$sql = "SELECT IDLinking FROM Synonyms WHERE Descriptor = '" . mysqli_real_escape_string($conn, $desc1) . "' LIMIT 1";
$res = mysqli_query($conn, $sql);
$idLinking = null;
if ($res && mysqli_num_rows($res) > 0) {
$row = mysqli_fetch_assoc($res);
$idLinking = $row['IDLinking'];
}
// Prüfen ob text2 bereits eine IDLinking hat
$sql2 = "SELECT IDLinking FROM Synonyms WHERE Descriptor = '" . mysqli_real_escape_string($conn, $desc2) . "' LIMIT 1";
$res2 = mysqli_query($conn, $sql2);
$idLinking2 = null;
if ($res2 && mysqli_num_rows($res2) > 0) {
$row2 = mysqli_fetch_assoc($res2);
$idLinking2 = $row2['IDLinking'];
}
// Fall 1: Beide haben noch keine IDLinking - neue Gruppe erstellen
if ($idLinking === null && $idLinking2 === null) {
// Höchste IDLinking finden und +1
$sqlMax = "SELECT MAX(IDLinking) as maxId FROM Synonyms";
$resMax = mysqli_query($conn, $sqlMax);
$rowMax = mysqli_fetch_assoc($resMax);
$newIdLinking = ($rowMax['maxId'] ?? 0) + 1;
// Beide Begriffe einfügen
$sqlInsert1 = "INSERT INTO Synonyms (IDLinking, Text, Descriptor) VALUES ($newIdLinking, '" . mysqli_real_escape_string($conn, $text1) . "', '" . mysqli_real_escape_string($conn, $desc1) . "')";
$sqlInsert2 = "INSERT INTO Synonyms (IDLinking, Text, Descriptor) VALUES ($newIdLinking, '" . mysqli_real_escape_string($conn, $text2) . "', '" . mysqli_real_escape_string($conn, $desc2) . "')";
mysqli_query($conn, $sqlInsert1);
mysqli_query($conn, $sqlInsert2);
echo json_encode(['success' => true, 'message' => 'Neue Synonym-Gruppe erstellt', 'idLinking' => $newIdLinking]);
}
// Fall 2: text1 hat bereits eine IDLinking - text2 zur Gruppe hinzufügen
else if ($idLinking !== null && $idLinking2 === null) {
$sqlInsert = "INSERT INTO Synonyms (IDLinking, Text, Descriptor) VALUES ($idLinking, '" . mysqli_real_escape_string($conn, $text2) . "', '" . mysqli_real_escape_string($conn, $desc2) . "')";
mysqli_query($conn, $sqlInsert);
echo json_encode(['success' => true, 'message' => 'Synonym zur bestehenden Gruppe hinzugefügt', 'idLinking' => $idLinking]);
}
// Fall 3: text2 hat bereits eine IDLinking - text1 zur Gruppe hinzufügen
else if ($idLinking === null && $idLinking2 !== null) {
$sqlInsert = "INSERT INTO Synonyms (IDLinking, Text, Descriptor) VALUES ($idLinking2, '" . mysqli_real_escape_string($conn, $text1) . "', '" . mysqli_real_escape_string($conn, $desc1) . "')";
mysqli_query($conn, $sqlInsert);
echo json_encode(['success' => true, 'message' => 'Begriff zur bestehenden Synonym-Gruppe hinzugefügt', 'idLinking' => $idLinking2]);
}
// Fall 4: Beide haben bereits IDLinking - Gruppen zusammenführen
else {
if ($idLinking == $idLinking2) {
echo json_encode(['success' => false, 'error' => 'Diese Begriffe sind bereits Synonyme']);
} else {
// Alle Einträge von idLinking2 auf idLinking umstellen
$sqlUpdate = "UPDATE Synonyms SET IDLinking = $idLinking WHERE IDLinking = $idLinking2";
mysqli_query($conn, $sqlUpdate);
echo json_encode(['success' => true, 'message' => 'Synonym-Gruppen zusammengeführt', 'idLinking' => $idLinking]);
}
}
}
else if ($action === 'remove') {
// Synonym entfernen (nur den einen Eintrag löschen)
$sqlDelete = "DELETE FROM Synonyms WHERE Descriptor = '" . mysqli_real_escape_string($conn, $desc2) . "'";
mysqli_query($conn, $sqlDelete);
if (mysqli_affected_rows($conn) > 0) {
echo json_encode(['success' => true, 'message' => 'Synonym entfernt']);
} else {
echo json_encode(['success' => false, 'error' => 'Synonym nicht gefunden']);
}
}
else {
echo json_encode(['success' => false, 'error' => 'Unbekannte Aktion']);
}
mysqli_close($conn);
?>

75
ajax/searchTreeData.php Normal file
View File

@ -0,0 +1,75 @@
<?php
/**
* searchTreeData.php - Sucht in der Treeview-Tabelle
*
* Parameter:
* - q: Suchbegriff (führendes * = Linkstrunkierung, abschließendes * = Rechtstrunkierung)
* Ohne * automatische Rechtstrunkierung (bisheriges Verhalten)
*
* Rückgabe: JSON-Array mit gefundenen Einträgen
*/
header('Content-Type: application/json; charset=utf-8');
include "db_connection.php";
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
if (mysqli_connect_errno()) {
echo json_encode([]);
exit();
}
mysqli_set_charset($conn, "utf8");
$searchTerm = isset($_GET['q']) ? trim($_GET['q']) : '';
if (strlen($searchTerm) < 2) {
echo json_encode([]);
exit();
}
// Trunkierung auswerten
$leftTrunc = (substr($searchTerm, 0, 1) === '*');
$rightTrunc = (substr($searchTerm, -1) === '*');
// Sternchen entfernen, dann escapen
$clean = trim($searchTerm, '*');
if (strlen($clean) < 2) {
echo json_encode([]);
exit();
}
$escaped = mysqli_real_escape_string($conn, $clean);
// LIKE-Sonderzeichen in den Daten escapen
$escaped = str_replace(['%', '_'], ['\\%', '\\_'], $escaped);
// Pattern bauen:
// *Begriff → %Begriff% (links- und rechtstrunkiert)
// Begriff* → Begriff% (nur rechtstrunkiert)
// *Begriff* → %Begriff% (beidseitig)
// Begriff → Begriff% (Standard: Rechtstrunkierung)
$pattern = ($leftTrunc ? '%' : '') .
$escaped .
($rightTrunc ? '%' : '%'); // rechts immer trunkieren = bisheriges Verhalten
$sql = "SELECT id, name, text, parent_id
FROM Treeview
WHERE LOWER(text) LIKE LOWER('$pattern') OR LOWER(name) LIKE LOWER('$pattern')
ORDER BY text
LIMIT 50";
$res = mysqli_query($conn, $sql);
if (!$res) {
echo json_encode([]);
exit();
}
$result = [];
while ($row = mysqli_fetch_assoc($res)) {
$result[] = [
'id' => strval($row['id']),
'text' => $row['text'] ? $row['text'] : $row['name'],
'parent_id' => $row['parent_id']
];
}
mysqli_close($conn);
echo json_encode($result, JSON_UNESCAPED_UNICODE);
?>

113
ajax/writeNewRelation.php Normal file
View File

@ -0,0 +1,113 @@
<?php
/**
* writeNewRelation.php - Speichert eine neue Relation
*
* Parameter (POST):
* - IDAnchor: ID des Ausgangseintrags
* - IDEntry: ID des Zieleintrags
* - Relationtype: Art der Relation (BT, NT, RT, USE, UF)
*/
header('Content-Type: application/json; charset=utf-8');
include "db_connection.php";
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
if (mysqli_connect_errno()) {
echo json_encode(['success' => false, 'error' => 'Datenbankverbindung fehlgeschlagen']);
exit();
}
mysqli_set_charset($conn, "utf8");
// Parameter aus JavaScript (AnchorID, relationType, relationID)
$idAnchor = isset($_POST['AnchorID']) ? intval($_POST['AnchorID']) : 0;
if ($idAnchor === 0) {
$idAnchor = isset($_POST['IDAnchor']) ? intval($_POST['IDAnchor']) : 0;
}
$idEntry = isset($_POST['relationID']) ? intval($_POST['relationID']) : 0;
if ($idEntry === 0) {
$idEntry = isset($_POST['IDEntry']) ? intval($_POST['IDEntry']) : 0;
}
$relationtype = isset($_POST['relationType']) ? trim($_POST['relationType']) : '';
if (empty($relationtype)) {
$relationtype = isset($_POST['Relationtype']) ? trim($_POST['Relationtype']) : '';
}
if ($idAnchor === 0 || $idEntry === 0) {
echo json_encode(['success' => false, 'error' => 'IDAnchor und IDEntry müssen angegeben werden']);
exit();
}
if (empty($relationtype)) {
echo json_encode(['success' => false, 'error' => 'Relationtype muss angegeben werden']);
exit();
}
// Erlaubte Relationstypen
$allowedTypes = ['BT', 'NT', 'RT', 'USE', 'UF'];
if (!in_array($relationtype, $allowedTypes)) {
echo json_encode(['success' => false, 'error' => 'Ungültiger Relationstyp']);
exit();
}
// Prüfen ob Relation bereits existiert
$checkSql = "SELECT ID FROM Linking WHERE IDAnchor = $idAnchor AND IDEntry = $idEntry AND Relationtype = '" . mysqli_real_escape_string($conn, $relationtype) . "'";
$checkResult = mysqli_query($conn, $checkSql);
if ($checkResult && mysqli_num_rows($checkResult) > 0) {
echo json_encode(['success' => false, 'error' => 'Diese Relation existiert bereits']);
mysqli_close($conn);
exit();
}
// Relation einfügen
$insertSql = "INSERT INTO Linking (IDAnchor, IDEntry, Relationtype) VALUES ($idAnchor, $idEntry, '" . mysqli_real_escape_string($conn, $relationtype) . "')";
if (mysqli_query($conn, $insertSql)) {
$newId = mysqli_insert_id($conn);
// Reziproke Relation erstellen (BT <-> NT, RT <-> RT)
$reciprocalType = null;
switch ($relationtype) {
case 'BT':
$reciprocalType = 'NT';
break;
case 'NT':
$reciprocalType = 'BT';
break;
case 'RT':
$reciprocalType = 'RT';
break;
case 'USE':
$reciprocalType = 'UF';
break;
case 'UF':
$reciprocalType = 'USE';
break;
}
if ($reciprocalType) {
// Prüfen ob reziproke Relation schon existiert
$checkRecSql = "SELECT ID FROM Linking WHERE IDAnchor = $idEntry AND IDEntry = $idAnchor AND Relationtype = '$reciprocalType'";
$checkRecResult = mysqli_query($conn, $checkRecSql);
if (!$checkRecResult || mysqli_num_rows($checkRecResult) === 0) {
$insertRecSql = "INSERT INTO Linking (IDAnchor, IDEntry, Relationtype) VALUES ($idEntry, $idAnchor, '$reciprocalType')";
mysqli_query($conn, $insertRecSql);
}
}
echo json_encode([
'success' => true,
'message' => 'Relation erfolgreich erstellt',
'id' => $newId
]);
} else {
echo json_encode(['success' => false, 'error' => 'Fehler beim Speichern: ' . mysqli_error($conn)]);
}
mysqli_close($conn);
?>

View File

@ -0,0 +1,115 @@
<?php
/**
* getAuthorityCount.php - Zählt DSpace-Datensätze für einen Authority-Begriff
*
* Parameter:
* - authType: 'Subject' oder 'Person'
* - query: Der zu suchende Begriff (exakte Übereinstimmung)
*
* Response: JSON mit count
*
* Beispiel:
* curl "https://bibb-dspace.bibb.de/DSpaceAPI/getAuthorityCount.php?authType=Subject&query=Berufsbildung"
* => {"query":"Berufsbildung","authType":"Subject","count":42}
*/
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
// IP-Prüfung
require_once __DIR__ . '/checkAccess.php';
// Parameter
$authType = isset($_GET['authType']) ? trim($_GET['authType']) : '';
$query = isset($_GET['query']) ? trim($_GET['query']) : '';
// Validierung
$allowedTypes = array('Subject', 'Person');
if (!in_array($authType, $allowedTypes)) {
http_response_code(400);
echo json_encode(array('error' => 'Invalid authType. Allowed: Subject, Person'));
exit;
}
if (strlen($query) === 0) {
http_response_code(400);
echo json_encode(array('error' => 'Missing parameter: query'));
exit;
}
// Solr-Feld je nach authType
switch ($authType) {
case 'Subject':
$queryField = 'dc.subject.other';
break;
case 'Person':
$queryField = 'dc.contributor.author';
break;
}
// Query bereinigen
$query = urldecode($query);
$query = str_replace("'", "", $query);
// Solr-Abfrage
$solr_baseurl = "http://localhost:8080/solr/search/query?";
$solr_query = $queryField . ':' . $query;
$rows = "&rows=10000";
$fl = "&fl=" . $queryField;
$solr_URL = $solr_baseurl . "wt=phps" . $fl . $rows . "&q=" . urlencode($solr_query . " AND withdrawn:false");
$context = stream_context_create(array(
'http' => array('timeout' => 10)
));
$response = @file_get_contents($solr_URL, false, $context);
if ($response === false) {
http_response_code(500);
echo json_encode(array('error' => 'Solr query failed'));
exit;
}
$result = unserialize($response);
if (!isset($result['response']['docs'])) {
echo json_encode(array(
'query' => $query,
'authType' => $authType,
'count' => 0
));
exit;
}
// Exakte Treffer zählen (analog zu DSpaceCountModel)
$count = 0;
foreach ($result['response']['docs'] as $entry) {
foreach ($entry as $hit) {
if (!is_array($hit)) {
$hit = array($hit);
}
foreach ($hit as $h) {
if ($authType === "Subject") {
if (strcmp($query, trim($h)) === 0) {
$count++;
}
} else {
// Person: Authority-ID in eckigen Klammern abschneiden
$name = trim(substr($h, 0, strpos($h, "[")));
if (strlen($name) === 0) {
$name = trim($h);
}
if (strcmp($query, $name) === 0) {
$count++;
}
}
}
}
}
echo json_encode(array(
'query' => $query,
'authType' => $authType,
'count' => $count
));
?>

46
getTreePath.php Normal file
View File

@ -0,0 +1,46 @@
<?php
/**
* getTreePath.php - Liefert den Pfad von einem Knoten bis zur Wurzel
*
* Parameter (GET):
* - id: Treeview-ID des Zielknotens
*
* Rückgabe: JSON-Array mit IDs von Root bis Zielknoten (inklusive)
* Beispiel: [98106, 98107, 98113, 98115]
*/
header('Content-Type: application/json; charset=utf-8');
include "db_connection.php";
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
if (mysqli_connect_errno()) {
echo json_encode([]);
exit();
}
mysqli_set_charset($conn, "utf8");
$targetId = isset($_GET['id']) ? intval($_GET['id']) : 0;
if ($targetId === 0) {
echo json_encode([]);
exit();
}
// Pfad aufbauen: vom Zielknoten nach oben bis Root (parent_id = 0 oder leer)
$path = [];
$curId = $targetId;
$limit = 20; // Schutz vor Endlosschleife bei defekten Daten
while ($curId > 0 && $limit-- > 0) {
$res = mysqli_query($conn, "SELECT id, parent_id FROM Treeview WHERE id = $curId LIMIT 1");
if (!$res) break;
$row = mysqli_fetch_assoc($res);
if (!$row) break;
array_unshift($path, intval($row['id'])); // vorne einfügen → Root zuerst
$parentId = intval($row['parent_id']);
if ($parentId === 0) break;
$curId = $parentId;
}
mysqli_close($conn);
echo json_encode($path, JSON_UNESCAPED_UNICODE);
?>

539
index.php Normal file
View File

@ -0,0 +1,539 @@
<?php
include "templates/Header.html";
?>
<style>
/* Dashboard Styles */
.dashboard-section {
margin-bottom: 30px;
}
.section-title {
font-size: 1.25rem;
font-weight: 600;
color: var(--primary-color);
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid var(--primary-color);
display: flex;
align-items: center;
gap: 10px;
}
/* Statistik-Karten */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.stat-card {
background: white;
border-radius: var(--radius-md);
padding: 20px;
border: 1px solid var(--border-color);
text-align: center;
transition: transform 0.2s, box-shadow 0.2s;
}
.stat-card:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-md);
}
.stat-icon {
font-size: 2rem;
margin-bottom: 10px;
}
.stat-value {
font-size: 2rem;
font-weight: 700;
color: var(--primary-color);
line-height: 1;
}
.stat-label {
font-size: 0.875rem;
color: var(--text-muted);
margin-top: 5px;
}
.stat-card.subject { border-top: 4px solid #28a745; }
.stat-card.subject .stat-icon { color: #28a745; }
.stat-card.person { border-top: 4px solid #007bff; }
.stat-card.person .stat-icon { color: #007bff; }
.stat-card.corporate { border-top: 4px solid #fd7e14; }
.stat-card.corporate .stat-icon { color: #fd7e14; }
.stat-card.publisher { border-top: 4px solid #6f42c1; }
.stat-card.publisher .stat-icon { color: #6f42c1; }
.stat-card.classification { border-top: 4px solid #20c997; }
.stat-card.classification .stat-icon { color: #20c997; }
.stat-card.total { border-top: 4px solid var(--primary-color); }
.stat-card.total .stat-icon { color: var(--primary-color); }
/* Zwei-Spalten Layout */
.dashboard-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 20px;
}
/* Listen-Karten */
.list-card {
background: white;
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
overflow: hidden;
}
.list-card-header {
background: var(--bg-light);
padding: 12px 15px;
font-weight: 600;
color: var(--text-primary);
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
gap: 8px;
}
.list-card-body {
padding: 0;
max-height: 300px;
overflow-y: auto;
}
.list-item {
padding: 10px 15px;
border-bottom: 1px solid var(--border-light);
display: flex;
justify-content: space-between;
align-items: center;
transition: background 0.2s;
}
.list-item:hover {
background: var(--bg-light);
}
.list-item:last-child {
border-bottom: none;
}
.list-item-text {
flex: 1;
font-weight: 500;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-right: 10px;
}
.list-item-meta {
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
}
.list-item-type {
font-size: 11px;
padding: 2px 8px;
border-radius: 10px;
background: var(--bg-light);
color: var(--text-muted);
}
.list-item-date {
font-size: 12px;
color: var(--text-muted);
}
.list-item-link {
color: var(--primary-color);
text-decoration: none;
}
.list-item-link:hover {
text-decoration: underline;
}
/* Qualitäts-Indikatoren */
.quality-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px;
}
.quality-item {
background: white;
border-radius: var(--radius-sm);
padding: 12px;
border: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
}
.quality-label {
font-size: 0.875rem;
color: var(--text-muted);
}
.quality-value {
font-weight: 600;
font-size: 1.1rem;
}
.quality-value.warning {
color: #dc3545;
}
.quality-value.ok {
color: #28a745;
}
/* Loading */
.loading-spinner {
display: flex;
justify-content: center;
align-items: center;
padding: 40px;
color: var(--text-muted);
}
.loading-spinner i {
font-size: 2rem;
margin-right: 10px;
}
/* Navigation Cards */
.nav-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.nav-card {
background: white;
border-radius: var(--radius-md);
padding: 25px 20px;
border: 1px solid var(--border-color);
text-align: center;
text-decoration: none;
color: var(--text-primary);
transition: all 0.2s;
}
.nav-card:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-md);
border-color: var(--primary-color);
color: var(--primary-color);
}
.nav-card-icon {
font-size: 2.5rem;
margin-bottom: 10px;
}
.nav-card-title {
font-weight: 600;
font-size: 1.1rem;
}
</style>
<!-- Main Content -->
<main class="main-content">
<div class="container">
<!-- Navigation -->
<div class="card dashboard-section">
<h2 class="section-title">
<i class="fas fa-th-large"></i> Normdateien verwalten
</h2>
<div class="nav-grid">
<a href="Subjects.php" class="nav-card">
<div class="nav-card-icon">🏷️</div>
<div class="nav-card-title">Schlagworte</div>
</a>
<a href="Persons.php" class="nav-card">
<div class="nav-card-icon">👥</div>
<div class="nav-card-title">Personen</div>
</a>
<a href="Corporates.php" class="nav-card">
<div class="nav-card-icon">🏢</div>
<div class="nav-card-title">Körperschaften</div>
</a>
<a href="Publishers.php" class="nav-card">
<div class="nav-card-icon">📚</div>
<div class="nav-card-title">Verlage</div>
</a>
<a href="Classifications.php" class="nav-card">
<div class="nav-card-icon">🗂️</div>
<div class="nav-card-title">Klassifikation</div>
</a>
<a href="Treeview.php" class="nav-card">
<div class="nav-card-icon">🌳</div>
<div class="nav-card-title">Klassifikationsbaum</div>
</a>
</div>
</div>
<!-- Übersicht Card -->
<div class="card dashboard-section">
<h2 class="section-title">
<i class="fas fa-chart-bar"></i> Übersicht
</h2>
<!-- Statistik-Karten -->
<div id="stats-container">
<div class="loading-spinner">
<i class="fas fa-circle-notch fa-spin"></i>
<span>Lade Statistiken...</span>
</div>
</div>
</div>
<!-- Aktivität & Qualität -->
<div class="dashboard-row dashboard-section">
<!-- Zuletzt geändert -->
<div class="list-card">
<div class="list-card-header">
<i class="fas fa-clock"></i> Zuletzt geändert
</div>
<div class="list-card-body" id="recently-modified">
<div class="loading-spinner">
<i class="fas fa-circle-notch fa-spin"></i>
</div>
</div>
</div>
<!-- Zuletzt hinzugefügt -->
<div class="list-card">
<div class="list-card-header">
<i class="fas fa-plus-circle"></i> Zuletzt hinzugefügt
</div>
<div class="list-card-body" id="recently-created">
<div class="loading-spinner">
<i class="fas fa-circle-notch fa-spin"></i>
</div>
</div>
</div>
</div>
<!-- Qualitäts-Check -->
<div class="card dashboard-section">
<h2 class="section-title">
<i class="fas fa-check-circle"></i> Qualitäts-Check
</h2>
<p class="text-muted mb-15">Einträge ohne Relationen (Waisen)</p>
<div id="quality-container">
<div class="loading-spinner">
<i class="fas fa-circle-notch fa-spin"></i>
</div>
</div>
</div>
</div>
</main>
<script>
$(document).ready(function() {
loadDashboardStats();
});
function loadDashboardStats() {
$.get('/Thesaurus/ajax/getDashboardStats.php', function(data) {
var stats = typeof data === 'string' ? JSON.parse(data) : data;
if (stats.error) {
$('#stats-container').html('<div class="text-center text-muted p-4">Fehler beim Laden der Statistiken</div>');
return;
}
// Statistik-Karten rendern
renderStatsCards(stats);
// Zuletzt geändert
renderRecentList('#recently-modified', stats.recentlyModified);
// Zuletzt hinzugefügt
renderRecentList('#recently-created', stats.recentlyCreated);
// Qualitäts-Check
renderQualityCheck(stats);
}).fail(function() {
$('#stats-container').html('<div class="text-center text-muted p-4">Fehler beim Laden der Statistiken</div>');
});
}
function renderStatsCards(stats) {
var html = '<div class="stats-grid">';
html += '<div class="stat-card subject">';
html += '<div class="stat-icon">🏷️</div>';
html += '<div class="stat-value">' + formatNumber(stats.counts.Subject) + '</div>';
html += '<div class="stat-label">Schlagworte</div>';
html += '</div>';
html += '<div class="stat-card person">';
html += '<div class="stat-icon">👥</div>';
html += '<div class="stat-value">' + formatNumber(stats.counts.Person) + '</div>';
html += '<div class="stat-label">Personen</div>';
html += '</div>';
html += '<div class="stat-card corporate">';
html += '<div class="stat-icon">🏢</div>';
html += '<div class="stat-value">' + formatNumber(stats.counts.Corporate) + '</div>';
html += '<div class="stat-label">Körperschaften</div>';
html += '</div>';
html += '<div class="stat-card publisher">';
html += '<div class="stat-icon">📚</div>';
html += '<div class="stat-value">' + formatNumber(stats.counts.Publisher) + '</div>';
html += '<div class="stat-label">Verlage</div>';
html += '</div>';
html += '<div class="stat-card classification">';
html += '<div class="stat-icon">🗂️</div>';
html += '<div class="stat-value">' + formatNumber(stats.counts.Classification) + '</div>';
html += '<div class="stat-label">Klassifikationen</div>';
html += '</div>';
html += '<div class="stat-card total">';
html += '<div class="stat-icon"><i class="fas fa-database"></i></div>';
html += '<div class="stat-value">' + formatNumber(stats.total) + '</div>';
html += '<div class="stat-label">Gesamt</div>';
html += '</div>';
html += '</div>';
// Zusätzliche Info-Zeile
html += '<div class="d-flex gap-4 mt-15 text-muted" style="font-size: 0.9rem;">';
html += '<span><i class="fas fa-link me-1"></i> ' + formatNumber(stats.relationsTotal) + ' Relationen</span>';
html += '<span><i class="fas fa-check me-1"></i> ' + formatNumber(stats.descriptors) + ' Deskriptoren</span>';
html += '<span><i class="fas fa-times me-1"></i> ' + formatNumber(stats.nonDescriptors) + ' Non-Deskriptoren</span>';
html += '</div>';
$('#stats-container').html(html);
}
function renderRecentList(container, items) {
if (!items || items.length === 0) {
$(container).html('<div class="text-center text-muted p-4">Keine Einträge</div>');
return;
}
var html = '';
items.forEach(function(item) {
var typeLabel = getTypeLabel(item.type);
var link = getTypeLink(item.type, item.id);
var date = formatDate(item.date);
html += '<div class="list-item">';
html += '<a href="' + link + '" class="list-item-text list-item-link" title="' + escapeHtml(item.text) + '">' + escapeHtml(item.text) + '</a>';
html += '<div class="list-item-meta">';
html += '<span class="list-item-type">' + typeLabel + '</span>';
html += '<span class="list-item-date">' + date + '</span>';
html += '</div>';
html += '</div>';
});
$(container).html(html);
}
function renderQualityCheck(stats) {
var html = '<div class="quality-grid">';
var types = [
{ key: 'Subject', label: 'Schlagworte', icon: '🏷️' },
{ key: 'Person', label: 'Personen', icon: '👥' },
{ key: 'Corporate', label: 'Körperschaften', icon: '🏢' },
{ key: 'Publisher', label: 'Verlage', icon: '📚' },
{ key: 'Classification', label: 'Klassifikation', icon: '🗂️' }
];
types.forEach(function(type) {
var count = stats.orphans[type.key] || 0;
var valueClass = count > 0 ? 'warning' : 'ok';
html += '<div class="quality-item">';
html += '<span class="quality-label">' + type.icon + ' ' + type.label + '</span>';
html += '<span class="quality-value ' + valueClass + '">' + formatNumber(count) + '</span>';
html += '</div>';
});
html += '</div>';
// Gesamt-Waisen
var totalOrphans = stats.orphansTotal || 0;
html += '<div class="mt-15 text-muted" style="font-size: 0.9rem;">';
html += '<i class="fas fa-exclamation-triangle me-1"></i> ';
html += 'Gesamt: <strong>' + formatNumber(totalOrphans) + '</strong> Einträge ohne Relationen';
html += '</div>';
$('#quality-container').html(html);
}
function getTypeLabel(type) {
var labels = {
'Subject': 'Schlagwort',
'Person': 'Person',
'Corporate': 'Körperschaft',
'Publisher': 'Verlag',
'Classification': 'Klassifikation'
};
return labels[type] || type;
}
function getTypeLink(type, id) {
var links = {
'Subject': 'Subjects.php?edit=',
'Person': 'Persons.php?edit=',
'Corporate': 'Corporates.php?edit=',
'Publisher': 'Publishers.php?edit=',
'Classification': 'Classifications.php?edit='
};
return (links[type] || 'Subjects.php?edit=') + id;
}
function formatNumber(num) {
if (!num) return '0';
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
}
function formatDate(dateStr) {
if (!dateStr || dateStr === '0000-00-00 00:00:00') return '-';
var date = new Date(dateStr);
return date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
}
function escapeHtml(text) {
if (!text) return '';
var div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
</script>
<?php
include "templates/Footer.html";
?>

639
js/ClassificationScript.js Normal file
View File

@ -0,0 +1,639 @@
/**
* Created by Roland on 2021/07/01.
* Updated for BIBB Layout 2024
*/
// CLASSIFICATIONS
// Show blank screen to add new Classification
function newClassificationsShow() {
// Sichere Element-Zugriffe mit Null-Checks
var saveBtn = document.getElementById("newClassificationSave");
var dismissBtn = document.getElementById("newClassificationDismiss");
var modifyBtn = document.getElementById("ClassificationModifySave");
var termField = document.getElementById("new_term");
var notationField = document.getElementById("new_notation");
var scopenoteField = document.getElementById("new_scopenote");
var relationLabel = document.getElementById("new_relation_label");
var relationDismissBtn = document.getElementById("newRelationClassificationDismiss");
var relationTable = document.getElementById("relation_table");
var errorDiv = document.getElementById("errorNewClassification");
if (saveBtn) saveBtn.style.display = 'inline';
if (dismissBtn) dismissBtn.style.display = 'inline';
if (modifyBtn) modifyBtn.style.display = 'none';
if (termField) termField.disabled = false;
if (notationField) notationField.disabled = false;
if (scopenoteField) scopenoteField.disabled = false;
// Relationen-Bereich verstecken bei neuer Klassifikation
if (relationLabel) relationLabel.style.display = 'none';
if (relationDismissBtn) relationDismissBtn.style.display = 'none';
showRelations(0,0,0);
if (relationTable) relationTable.style.display = 'none';
if (errorDiv) errorDiv.style.display = 'none';
$("#new_term").val("");
$("#new_notation").val("");
$("#new_scopenote").val("");
$("#NewClassificationModal").modal("show");
}
// Insert new classification record
function CreateNewEntry(type) {
// get values
var term = $("#new_term").val();
term = term.trim();
var notation = $("#new_notation").val();
notation = notation.trim();
var detailtype = "";
var classification = "";
var scopenote = $("#new_scopenote").val();
scopenote = scopenote.trim();
if (term.length == 0) {
var a = document.getElementById("errorNewClassification");
a.classList.add('alert-danger');
a.textContent = 'Bitte Klassifikation eingeben!';
a.style.display = 'block';
return;
}
var a = document.getElementById("errorNewClassification");
a.classList.remove('alert-danger');
a.textContent = '';
a.style.display = 'none';
$.post("ajax/newClassification.php", {
term : term,
type : type,
notation : notation,
detailtype : detailtype,
classification: classification,
scopenote : scopenote
}, function (data, status) {
var dt = (typeof data === 'string') ? JSON.parse(data) : data;
var e = document.getElementById("errorNewClassification");
if (dt['status'] == 200)
{
var AnchorID = dt["Anchor"];
var EntryID = dt["Entry"];
var LinkingID = dt["Linking"];
var b = document.getElementById("newClassificationSave");
b.style.display='none';
var c = document.getElementById("newClassificationDismiss");
c.style.display='none';
document.getElementById("new_term").disabled = true;
document.getElementById("new_notation").disabled = true;
document.getElementById("new_scopenote").disabled = true;
$table.bootstrapTable('refresh');
e.classList.remove('alert-danger');
e.classList.add('alert-success');
e.textContent = 'Neue Klassifikation aufgenommen';
e.style.display = 'block';
// ID setzen für Relationen
$('#ID').val(AnchorID);
// Relationen-Bereich anzeigen nach Speichern
document.getElementById("new_relation_label").style.display = 'block';
document.getElementById("newRelationClassificationDismiss").style.display = 'inline';
showRelations(AnchorID,EntryID,LinkingID);
} else {
e.classList.remove('alert-success');
e.classList.add('alert-danger');
e.textContent = dt['message'];
e.style.display = 'block';
return;
}
});
}
// Show relations of a classification record
function showRelations(Anchor,Entry,Linking)
{
$.get("ajax/getRelations.php", {
anchorID:Anchor,
authType:"Classification"
},
function (data, status) {
if (status=="success")
{
$(".new_modal_content").html(data);
// Tabelle nur anzeigen wenn Relationen vorhanden
var relationTable = document.getElementById("relation_table");
if (relationTable) {
if (data && data.trim().length > 0) {
relationTable.style.display = 'block';
} else {
relationTable.style.display = 'none';
}
}
$("#NewClassificationModal").modal("show");
}
}
);
}
// create new relation for a classification record
function CreateNewRelation(AnchorID, relationID, event) {
if (event) {
event.preventDefault();
event.stopPropagation();
}
console.log('🚀 CreateNewRelation:', { AnchorID, relationID });
try {
// Validierung
if (!AnchorID || !relationID) {
showError('Fehlende Parameter: AnchorID oder relationID');
return false;
}
var relationTypeElement = document.getElementById("new_relationtype");
if (!relationTypeElement) {
showError('Relationstyp-Element nicht gefunden');
return false;
}
var relationType = relationTypeElement.value;
if (!relationType || relationType.trim() === '') {
showError('Bitte wählen Sie einen Relationstyp aus');
return false;
}
// Loading anzeigen
showLoading();
$.ajax({
url: "ajax/writeNewRelation.php",
type: "POST",
data: {
AnchorID: AnchorID,
relationType: relationType,
relationID: relationID
},
dataType: "text",
timeout: 10000,
success: function(response) {
console.log("✅ Success! Response:", response);
try {
var data = JSON.parse(response);
if (data.error) {
showError('<strong>Serverfehler:</strong><br>' + data.error);
return;
}
if (data.success === true) {
showSuccess('Neue Relation erfolgreich aufgenommen!');
if (typeof showRelations === 'function') {
setTimeout(function() {
showRelations(AnchorID, 0, 0);
}, 500);
}
resetForm();
} else {
showError('Unerwartete Antwort vom Server');
}
} catch (parseError) {
console.log("⚠️ Keine JSON-Antwort, prüfe Plain-Text");
if (response && response.length > 0 && !isNaN(response.trim())) {
showSuccess('Neue Relation erfolgreich aufgenommen! (ID: ' + response.trim() + ')');
if (typeof showRelations === 'function') {
setTimeout(function() {
showRelations(AnchorID, 0, 0);
}, 500);
}
resetForm();
} else {
showError('<strong>Unerwartete Antwort:</strong><br>' +
'<code>' + response.substring(0, 200) + '</code>');
}
}
},
error: function(xhr, status, error) {
console.error("❌ AJAX Error:", { status, error, xhr });
var errorMessage = '<strong>Fehler beim Speichern der Relation</strong><br><br>';
if (xhr.status === 0) {
errorMessage += '❌ Keine Verbindung zum Server möglich<br>';
errorMessage += '<small>Prüfen Sie Ihre Internetverbindung</small>';
} else if (xhr.status === 404) {
errorMessage += '❌ Datei nicht gefunden (404)<br>';
errorMessage += '<small>ajax/writeNewRelation.php existiert nicht</small>';
} else if (xhr.status === 500) {
errorMessage += '❌ Interner Serverfehler (500)<br>';
if (xhr.responseText) {
errorMessage += '<br><details><summary>Details anzeigen</summary><pre>' +
xhr.responseText.substring(0, 500) + '</pre></details>';
}
} else if (status === 'timeout') {
errorMessage += '❌ Zeitüberschreitung<br>';
errorMessage += '<small>Der Server hat nicht rechtzeitig geantwortet</small>';
} else if (status === 'parsererror') {
errorMessage += '❌ Fehler beim Verarbeiten der Server-Antwort<br>';
errorMessage += '<small>Die Server-Antwort war ungültig</small>';
} else {
errorMessage += '❌ ' + status + ' (' + xhr.status + ')';
if (error) {
errorMessage += '<br><small>' + error + '</small>';
}
}
showError(errorMessage);
},
complete: function(xhr, status) {
console.log("🏁 Request abgeschlossen:", status);
if (status !== 'success') {
hideLoading();
}
}
});
return false;
} catch (e) {
hideLoading();
console.error("💥 Exception:", e);
showError('<strong>JavaScript-Fehler:</strong><br>' + e.message);
return false;
}
}
function showError(message) {
var errorElement = document.getElementById("errorNewClassification");
if (errorElement) {
errorElement.className = 'alert alert-danger';
errorElement.innerHTML = '<i class="fas fa-exclamation-circle me-2"></i>' + message.replace(/\n/g, '<br>');
errorElement.style.display = 'block';
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
setTimeout(function() {
fadeOutAlert();
}, 8000);
}
}
function showSuccess(message) {
var errorElement = document.getElementById("errorNewClassification");
if (errorElement) {
errorElement.className = 'alert alert-success';
errorElement.innerHTML = '<i class="fas fa-check-circle me-2"></i>' + message;
errorElement.style.display = 'block';
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
setTimeout(function() {
fadeOutAlert();
}, 3000);
}
}
function showWarning(message) {
var errorElement = document.getElementById("errorNewClassification");
if (errorElement) {
errorElement.className = 'alert alert-warning';
errorElement.innerHTML = '<i class="fas fa-exclamation-triangle me-2"></i>' + message;
errorElement.style.display = 'block';
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
setTimeout(function() {
fadeOutAlert();
}, 5000);
}
}
function showLoading() {
var errorElement = document.getElementById("errorNewClassification");
if (errorElement) {
errorElement.className = 'alert alert-info';
errorElement.innerHTML = '<div class="d-flex align-items-center"><div class="spinner-border spinner-border-sm me-3" role="status"><span class="visually-hidden">Laden...</span></div><div>Speichere Relation...</div></div>';
errorElement.style.display = 'block';
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}
function hideLoading() {
var errorElement = document.getElementById("errorNewClassification");
if (errorElement) {
errorElement.style.display = 'none';
}
}
function hideAlert() {
var errorElement = document.getElementById("errorNewClassification");
if (errorElement) {
errorElement.style.display = 'none';
}
}
function fadeOutAlert() {
var errorElement = document.getElementById("errorNewClassification");
if (errorElement && errorElement.style.display !== 'none') {
$(errorElement).fadeOut(500, function() {
errorElement.style.display = 'none';
});
}
}
function resetForm() {
var relationTextElement = document.getElementById("search_relation");
if (relationTextElement) {
relationTextElement.value = '';
}
$('#classification-anlegen').removeData('record-id');
$('#classification-anlegen').removeData('relation-id');
hideAlert();
}
// Delete relation for a classification
function DeleteRelation(AnchorID, LinkingID)
{
if (!confirm('Möchten Sie diese Relation wirklich löschen?')) {
return;
}
$.post("ajax/deleteRelation.php", {
AnchorID : AnchorID,
LinkingID : LinkingID
},
function (data, status) {
if (status=="success")
{
var e = document.getElementById("errorNewClassification");
e.classList.add('alert-success');
e.textContent = ' Relation gelöscht';
e.style.display = 'block';
showRelations(AnchorID,0,0)
}
}
);
}
// delete the whole classification entry
function DeleteTerm(AnchorID)
{
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
return;
}
$.post("ajax/deleteTerm.php", {
AnchorID: AnchorID[0]
},
function (data, status) {
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
if (status === "success" && metadata.success === true) {
// Tabelle neu laden Zeile ist serverseitig wirklich gelöscht
$table.bootstrapTable('refresh');
} else {
// Serverseitiger Fehler: Zeile wieder einblenden und Meldung anzeigen
$table.bootstrapTable('refresh');
alert('Fehler beim Löschen: ' + (metadata.error || 'Unbekannter Fehler'));
}
}
);
}
// Modify classification and its relations
function ModifyTerm(AnchorID)
{
$("#ID").val(AnchorID[0]);
// Sichere Element-Zugriffe mit Null-Checks
var termField = document.getElementById("new_term");
var notationField = document.getElementById("new_notation");
var scopenoteField = document.getElementById("new_scopenote");
var relationLabel = document.getElementById("new_relation_label");
var relationTable = document.getElementById("relation_table");
var saveBtn = document.getElementById("newClassificationSave");
var dismissBtn = document.getElementById("newClassificationDismiss");
var modifyBtn = document.getElementById("ClassificationModifySave");
var relationDismissBtn = document.getElementById("newRelationClassificationDismiss");
var errorDiv = document.getElementById("errorNewClassification");
// Name-Feld deaktivieren (darf nicht geändert werden)
if (termField) termField.disabled = true;
// Andere Felder aktivieren
if (notationField) notationField.disabled = false;
if (scopenoteField) scopenoteField.disabled = false;
// Relationen-Bereich sofort sichtbar machen bei Bearbeitung!
if (relationLabel) relationLabel.style.display = 'block';
if (relationTable) relationTable.style.display = 'block';
// Buttons konfigurieren
if (saveBtn) saveBtn.style.display = 'none';
if (dismissBtn) dismissBtn.style.display = 'inline';
if (modifyBtn) modifyBtn.style.display = 'inline';
if (relationDismissBtn) relationDismissBtn.style.display = 'none';
// Alert verstecken
if (errorDiv) errorDiv.style.display = 'none';
$("#NewClassificationModalHeadline").text("Klassifikation ändern");
$.get("/Thesaurus/ajax/getAuthorityDataRaw.php", {
id : AnchorID[0],
authType :"Classification"
},
function (data, status) {
console.log('📥 getAuthorityDataRaw status:', status);
console.log('📥 getAuthorityDataRaw data:', data);
if (status == "success")
{
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
console.log('📥 metadata:', metadata);
dt = (metadata['rows'][0]);
console.log('📥 dt:', dt);
if (dt) {
$("#new_term").val(dt.Text);
$("#new_id").val(dt.ID);
$("#new_notation").val(dt.Notation);
$("#new_descriptor").val(dt.Descriptor);
$("#new_scopenote").val(dt.Scopenote);
console.log('✅ Felder befüllt mit:', dt.Text);
} else {
console.error('❌ Keine Daten in rows[0]');
}
}
}
).fail(function(xhr, status, error) {
console.error('❌ AJAX Fehler:', status, error);
});
$.get("ajax/getRelations.php", {
anchorID:AnchorID[0],
authType: "Classification"
},
function (data, status) {
if (status=="success")
{
$(".new_modal_content").html(data);
$("#NewClassificationModal").modal("show");
// Autocomplete neu initialisieren nach Modal-Öffnung
setTimeout(function() {
if (typeof searchRelation === 'function') {
console.log('🔄 Autocomplete wird neu initialisiert...');
searchRelation();
}
}, 300);
}
}
);
}
// rewrite records after updating a classification entry
function UpdateEntry(authType)
{
var AnchorID = $("#ID").val();
var term = $("#new_term").val();
term = term.trim();
var notation = $("#new_notation").val();
notation = notation.trim();
var type = 'Classification';
var detailtype = '';
var classification = '';
var scopenote = $("#new_scopenote").val();
scopenote = scopenote.trim();
if (term.length == 0) {
var a = document.getElementById("errorNewClassification");
a.classList.add('alert-danger');
a.textContent = 'Bitte Klassifikation eingeben!';
a.style.display = 'block';
return;
}
var a = document.getElementById("errorNewClassification");
a.classList.remove('alert-danger');
a.textContent = '';
a.style.display = 'none';
$.post("ajax/UpdateTerm.php", {
AnchorID: AnchorID,
authType: "Classification",
term: term,
notation: notation,
type: type,
detailtype: detailtype,
classification: classification,
scopenote: scopenote
},
function (data, status) {
if (status=="success")
{
document.getElementById("relation_table").style.display = 'block'
document.getElementById("newClassificationSave").style.display = 'none'
document.getElementById("newClassificationDismiss").style.display = 'inline'
document.getElementById("ClassificationModifySave").style.display = 'none'
document.getElementById("newRelationClassificationDismiss").style.display = 'none'
var e = document.getElementById("errorNewClassification");
e.classList.add('alert-success');
e.textContent = ' Klassifikation geändert ';
e.style.display = 'block';
$("#NewClassificationModalHeadline").text("Klassifikation ändern");
$("#NewClassificationModal").modal("show");
}
}
);
$table.bootstrapTable('refresh')
}
function ShowModalDetails(id) {
$.get("/Thesaurus/ajax/getDetailAuthorityData.php", {
authType : 'Classification',
offset : 0,
id : id,
sort : 'Anchor.Text',
limit : 1
},
function(data, status) {
if (status == "success") {
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
var row = metadata['rows'];
//Assign existing values to the modal popup fields
$("#display_term").val(row[0].Text);
$("#display_id").val(row[0].ID);
$("#display_notation").val(row[0].Notation);
$("#display_descriptor").val(row[0].Descriptor);
$("#display_scopenote").val(row[0].Scopenote);
var html = []
var txt = ''
$.each(row[0].Relations, function (key, value) {
txt = txt +'<div class="col-sm-12">';
txt = txt +'<div class="col-sm-2"></div>';
txt = txt + '<div class="col-sm-3"><input type="text" class="form-control" disabled id="display_relationtype_' +key +'" /></div>';
txt = txt + '<div class="col-sm-5"><input type="text" class="form-control" disabled id="display_textrelation_' +key +'" /></div>';
txt = txt + '<div class="col-sm-2"><input type="text" class="form-control" disabled id="display_IDRelation_' +key +'" /></div>';
txt = txt +'</div>';
})
$(".modal_content").html(txt);
$.each(row[0].Relations, function (key, value) {
$("#display_relationtype_" +key).val(value['Relationtype']);
$("#display_textrelation_" +key).val(value['TextRelation']);
$("#display_IDRelation_" +key).val(value['IDRelation']);
})
$("#display_relations").val(txt);
}
}
);
$("#DetailsModal").modal("show");
}
function refreshPage()
{
location.reload();
}
function CopyLink(value)
{
var tempInput = document.createElement("input");
tempInput.value = value;
document.body.appendChild(tempInput);
tempInput.select();
document.execCommand("copy");
document.body.removeChild(tempInput);
}

633
js/CorporateScript.js Normal file
View File

@ -0,0 +1,633 @@
/**
* Created by Roland on 2021/07/01.
* Updated for BIBB Layout 2024
*/
// CORPORATES
// Show blank screen to add new Corporate
function newCorporatesShow() {
var b = document.getElementById("newCorporateSave");
b.style.display='inline';
var c = document.getElementById("newCorporateDismiss");
c.style.display='inline';
document.getElementById("CorporateModifySave").style.display = 'none';
document.getElementById("new_term").disabled = false;
document.getElementById("new_type").disabled = false;
document.getElementById("new_scopenote").disabled = false;
// Relationen-Bereich verstecken bei neuer Körperschaft
document.getElementById("new_relation_label").style.display = 'none';
document.getElementById("newRelationCorporateDismiss").style.display = 'none';
showRelations(0,0,0);
var a = document.getElementById("relation_table");
if (a != undefined) {
document.getElementById("relation_table").style.display = 'none';
}
var e = document.getElementById("errorNewCorporate");
e.style.display = 'none';
$("#new_term").val("");
$("#new_scopenote").val("");
$("#NewCorporateModal").modal("show");
}
// Insert new corporate record
function CreateNewEntry(type) {
// get values
var term = $("#new_term").val();
term = term.trim();
var type = $("#new_type").val();
type = type.trim();
var detailtype = "";
var classification = "";
var scopenote = $("#new_scopenote").val();
scopenote = scopenote.trim();
if (term.length == 0) {
var a = document.getElementById("errorNewCorporate");
a.classList.add('alert-danger');
a.textContent = 'Bitte Körperschaft eingeben!';
a.style.display = 'block';
return;
}
var a = document.getElementById("errorNewCorporate");
a.classList.remove('alert-danger');
a.textContent = '';
a.style.display = 'none';
$.post("ajax/newCorporate.php", {
term : term,
type : type,
detailtype : detailtype,
classification: classification,
scopenote : scopenote
}, function (data, status) {
var dt = (typeof data === 'string') ? JSON.parse(data) : data;
var e = document.getElementById("errorNewCorporate");
if (dt['status'] == 200)
{
var AnchorID = dt["Anchor"];
var EntryID = dt["Entry"];
var LinkingID = dt["Linking"];
var b = document.getElementById("newCorporateSave");
b.style.display='none';
var c = document.getElementById("newCorporateDismiss");
c.style.display='none';
document.getElementById("new_term").disabled = true;
document.getElementById("new_type").disabled = true;
document.getElementById("new_scopenote").disabled = true;
$table.bootstrapTable('refresh');
e.classList.remove('alert-danger');
e.classList.add('alert-success');
e.textContent = 'Neue Körperschaft aufgenommen';
e.style.display = 'block';
// ID setzen für Relationen
$('#ID').val(AnchorID);
// Relationen-Bereich anzeigen nach Speichern
document.getElementById("new_relation_label").style.display = 'block';
document.getElementById("newRelationCorporateDismiss").style.display = 'inline';
showRelations(AnchorID,EntryID,LinkingID);
} else {
e.classList.remove('alert-success');
e.classList.add('alert-danger');
e.textContent = dt['message'];
e.style.display = 'block';
return;
}
});
}
// Show relations of a corporate record
function showRelations(Anchor,Entry,Linking)
{
$.get("ajax/getRelations.php", {
anchorID:Anchor,
authType:"Corporate"
},
function (data, status) {
if (status=="success")
{
$(".new_modal_content").html(data);
// Tabelle nur anzeigen wenn Relationen vorhanden
var relationTable = document.getElementById("relation_table");
if (relationTable) {
if (data && data.trim().length > 0) {
relationTable.style.display = 'block';
} else {
relationTable.style.display = 'none';
}
}
$("#NewCorporateModal").modal("show");
}
}
);
}
// create new relation for a corporate record
function CreateNewRelation(AnchorID, relationID, event) {
if (event) {
event.preventDefault();
event.stopPropagation();
}
console.log('🚀 CreateNewRelation:', { AnchorID, relationID });
try {
// Validierung
if (!AnchorID || !relationID) {
showError('Fehlende Parameter: AnchorID oder relationID');
return false;
}
var relationTypeElement = document.getElementById("new_relationtype");
if (!relationTypeElement) {
showError('Relationstyp-Element nicht gefunden');
return false;
}
var relationType = relationTypeElement.value;
if (!relationType || relationType.trim() === '') {
showError('Bitte wählen Sie einen Relationstyp aus');
return false;
}
// Loading anzeigen
showLoading();
$.ajax({
url: "ajax/writeNewRelation.php",
type: "POST",
data: {
AnchorID: AnchorID,
relationType: relationType,
relationID: relationID
},
dataType: "text",
timeout: 10000,
success: function(response) {
console.log("✅ Success! Response:", response);
try {
var data = JSON.parse(response);
if (data.error) {
showError('<strong>Serverfehler:</strong><br>' + data.error);
return;
}
if (data.success === true) {
showSuccess('Neue Relation erfolgreich aufgenommen!');
if (typeof showRelations === 'function') {
setTimeout(function() {
showRelations(AnchorID, 0, 0);
}, 500);
}
resetForm();
} else {
showError('Unerwartete Antwort vom Server');
}
} catch (parseError) {
console.log("⚠️ Keine JSON-Antwort, prüfe Plain-Text");
if (response && response.length > 0 && !isNaN(response.trim())) {
showSuccess('Neue Relation erfolgreich aufgenommen! (ID: ' + response.trim() + ')');
if (typeof showRelations === 'function') {
setTimeout(function() {
showRelations(AnchorID, 0, 0);
}, 500);
}
resetForm();
} else {
showError('<strong>Unerwartete Antwort:</strong><br>' +
'<code>' + response.substring(0, 200) + '</code>');
}
}
},
error: function(xhr, status, error) {
console.error("❌ AJAX Error:", { status, error, xhr });
var errorMessage = '<strong>Fehler beim Speichern der Relation</strong><br><br>';
if (xhr.status === 0) {
errorMessage += '❌ Keine Verbindung zum Server möglich<br>';
errorMessage += '<small>Prüfen Sie Ihre Internetverbindung</small>';
} else if (xhr.status === 404) {
errorMessage += '❌ Datei nicht gefunden (404)<br>';
errorMessage += '<small>ajax/writeNewRelation.php existiert nicht</small>';
} else if (xhr.status === 500) {
errorMessage += '❌ Interner Serverfehler (500)<br>';
if (xhr.responseText) {
errorMessage += '<br><details><summary>Details anzeigen</summary><pre>' +
xhr.responseText.substring(0, 500) + '</pre></details>';
}
} else if (status === 'timeout') {
errorMessage += '❌ Zeitüberschreitung<br>';
errorMessage += '<small>Der Server hat nicht rechtzeitig geantwortet</small>';
} else if (status === 'parsererror') {
errorMessage += '❌ Fehler beim Verarbeiten der Server-Antwort<br>';
errorMessage += '<small>Die Server-Antwort war ungültig</small>';
} else {
errorMessage += '❌ ' + status + ' (' + xhr.status + ')';
if (error) {
errorMessage += '<br><small>' + error + '</small>';
}
}
showError(errorMessage);
},
complete: function(xhr, status) {
console.log("🏁 Request abgeschlossen:", status);
if (status !== 'success') {
hideLoading();
}
}
});
return false;
} catch (e) {
hideLoading();
console.error("💥 Exception:", e);
showError('<strong>JavaScript-Fehler:</strong><br>' + e.message);
return false;
}
}
function showError(message) {
var errorElement = document.getElementById("errorNewCorporate");
if (errorElement) {
errorElement.className = 'alert alert-danger';
errorElement.innerHTML = '<i class="fas fa-exclamation-circle me-2"></i>' + message.replace(/\n/g, '<br>');
errorElement.style.display = 'block';
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
setTimeout(function() {
fadeOutAlert();
}, 8000);
}
}
function showSuccess(message) {
var errorElement = document.getElementById("errorNewCorporate");
if (errorElement) {
errorElement.className = 'alert alert-success';
errorElement.innerHTML = '<i class="fas fa-check-circle me-2"></i>' + message;
errorElement.style.display = 'block';
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
setTimeout(function() {
fadeOutAlert();
}, 3000);
}
}
function showWarning(message) {
var errorElement = document.getElementById("errorNewCorporate");
if (errorElement) {
errorElement.className = 'alert alert-warning';
errorElement.innerHTML = '<i class="fas fa-exclamation-triangle me-2"></i>' + message;
errorElement.style.display = 'block';
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
setTimeout(function() {
fadeOutAlert();
}, 5000);
}
}
function showLoading() {
var errorElement = document.getElementById("errorNewCorporate");
if (errorElement) {
errorElement.className = 'alert alert-info';
errorElement.innerHTML = '<div class="d-flex align-items-center"><div class="spinner-border spinner-border-sm me-3" role="status"><span class="visually-hidden">Laden...</span></div><div>Speichere Relation...</div></div>';
errorElement.style.display = 'block';
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}
function hideLoading() {
var errorElement = document.getElementById("errorNewCorporate");
if (errorElement) {
errorElement.style.display = 'none';
}
}
function hideAlert() {
var errorElement = document.getElementById("errorNewCorporate");
if (errorElement) {
errorElement.style.display = 'none';
}
}
function fadeOutAlert() {
var errorElement = document.getElementById("errorNewCorporate");
if (errorElement && errorElement.style.display !== 'none') {
$(errorElement).fadeOut(500, function() {
errorElement.style.display = 'none';
});
}
}
function resetForm() {
var relationTextElement = document.getElementById("search_relation");
if (relationTextElement) {
relationTextElement.value = '';
}
$('#corporate-anlegen').removeData('record-id');
$('#corporate-anlegen').removeData('relation-id');
hideAlert();
}
// Delete relation for a corporate
function DeleteRelation(AnchorID, LinkingID)
{
if (!confirm('Möchten Sie diese Relation wirklich löschen?')) {
return;
}
$.post("ajax/deleteRelation.php", {
AnchorID : AnchorID,
LinkingID : LinkingID
},
function (data, status) {
if (status=="success")
{
var e = document.getElementById("errorNewCorporate");
e.classList.add('alert-success');
e.textContent = ' Relation gelöscht';
e.style.display = 'block';
showRelations(AnchorID,0,0)
}
}
);
}
// delete the whole corporate entry
function DeleteTerm(AnchorID)
{
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
return;
}
$.post("ajax/deleteTerm.php", {
AnchorID: AnchorID[0]
},
function (data, status) {
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
if (status === "success" && metadata.success === true) {
// Tabelle neu laden Zeile ist serverseitig wirklich gelöscht
$table.bootstrapTable('refresh');
} else {
// Serverseitiger Fehler: Zeile wieder einblenden und Meldung anzeigen
$table.bootstrapTable('refresh');
alert('Fehler beim Löschen: ' + (metadata.error || 'Unbekannter Fehler'));
}
}
);
}
// Modify corporate and its relations
function ModifyTerm(AnchorID)
{
// Name-Feld deaktivieren (darf nicht geändert werden)
document.getElementById("new_term").disabled = true;
// Andere Felder aktivieren
document.getElementById("new_type").disabled = false;
document.getElementById("new_scopenote").disabled = false;
$("#ID").val(AnchorID[0]);
// Relationen-Bereich sofort sichtbar machen bei Bearbeitung!
document.getElementById("new_relation_label").style.display = 'block';
document.getElementById("relation_table").style.display = 'block';
// Buttons konfigurieren
document.getElementById("newCorporateSave").style.display = 'none';
document.getElementById("newCorporateDismiss").style.display = 'inline';
document.getElementById("CorporateModifySave").style.display = 'inline';
document.getElementById("newRelationCorporateDismiss").style.display = 'none';
// Alert verstecken
document.getElementById("errorNewCorporate").style.display = 'none';
$("#NewCorporateModalHeadline").text("Körperschaft ändern");
$.get("/Thesaurus/ajax/getAuthorityDataRaw.php", {
id : AnchorID[0],
authType :"Corporate"
},
function (data, status) {
console.log('📥 getAuthorityDataRaw status:', status);
console.log('📥 getAuthorityDataRaw data:', data);
if (status == "success")
{
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
console.log('📥 metadata:', metadata);
dt = (metadata['rows'][0]);
console.log('📥 dt:', dt);
if (dt) {
$("#new_term").val(dt.Text);
$("#new_id").val(dt.ID);
$("#new_descriptor").val(dt.Descriptor);
$("#new_type").val(dt.Type);
$("#new_scopenote").val(dt.Scopenote);
console.log('✅ Felder befüllt mit:', dt.Text);
} else {
console.error('❌ Keine Daten in rows[0]');
}
}
}
).fail(function(xhr, status, error) {
console.error('❌ AJAX Fehler:', status, error);
});
$.get("ajax/getRelations.php", {
anchorID:AnchorID[0],
authType: "Corporate"
},
function (data, status) {
if (status=="success")
{
$(".new_modal_content").html(data);
$("#NewCorporateModal").modal("show");
// Autocomplete neu initialisieren nach Modal-Öffnung
setTimeout(function() {
if (typeof searchRelation === 'function') {
console.log('🔄 Autocomplete wird neu initialisiert...');
searchRelation();
}
}, 300);
}
}
);
}
// rewrite records after updating a corporate entry
function UpdateEntry(authType)
{
var AnchorID = $("#ID").val();
var term = $("#new_term").val();
term = term.trim();
var type = $("#new_type").val();
type = type.trim();
var detailtype = '';
var classification = '';
var scopenote = $("#new_scopenote").val();
scopenote = scopenote.trim();
if (term.length == 0) {
var a = document.getElementById("errorNewCorporate");
a.classList.add('alert-danger');
a.textContent = 'Bitte Körperschaft eingeben!';
a.style.display = 'block';
return;
}
var a = document.getElementById("errorNewCorporate");
a.classList.remove('alert-danger');
a.textContent = '';
a.style.display = 'none';
$.post("ajax/UpdateTerm.php", {
AnchorID: AnchorID,
authType: "Corporate",
term: term,
type: type,
detailtype: detailtype,
classification: classification,
scopenote: scopenote
},
function (data, status) {
if (status=="success")
{
document.getElementById("relation_table").style.display = 'block'
document.getElementById("newCorporateSave").style.display = 'none'
document.getElementById("newCorporateDismiss").style.display = 'inline'
document.getElementById("CorporateModifySave").style.display = 'none'
document.getElementById("newRelationCorporateDismiss").style.display = 'none'
var e = document.getElementById("errorNewCorporate");
e.classList.add('alert-success');
e.textContent = ' Körperschaft geändert ';
e.style.display = 'block';
$("#NewCorporateModalHeadline").text("Körperschaft ändern");
$("#NewCorporateModal").modal("show");
}
}
);
$table.bootstrapTable('refresh')
}
function ShowModalDetails(id) {
$.get("/Thesaurus/ajax/getDetailAuthorityData.php", {
authType : 'Corporate',
offset : 0,
id : id,
sort : 'Anchor.Text',
limit : 1
},
function(data, status) {
if (status == "success") {
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
var row = metadata['rows'];
if (!row || row.length === 0) {
console.error('❌ Keine Daten gefunden für ID:', id);
return;
}
//Assign existing values to the modal popup fields
$("#display_term").val(row[0].Text);
$("#display_id").val(row[0].ID);
$("#display_id_show").val(row[0].ID);
$("#display_descriptor").val(row[0].Descriptor);
$("#display_type").val(row[0].Type);
$("#display_scopenote").val(row[0].Scopenote);
var html = []
var txt = ''
$.each(row[0].Relations, function (key, value) {
txt = txt +'<div class="col-sm-12">';
txt = txt +'<div class="col-sm-2"></div>';
txt = txt + '<div class="col-sm-3"><input type="text" class="form-control" disabled id="display_relationtype_' +key +'" /></div>';
txt = txt + '<div class="col-sm-5"><input type="text" class="form-control" disabled id="display_textrelation_' +key +'" /></div>';
txt = txt + '<div class="col-sm-2"><input type="text" class="form-control" disabled id="display_IDRelation_' +key +'" /></div>';
txt = txt +'</div>';
})
$(".modal_content").html(txt);
$.each(row[0].Relations, function (key, value) {
$("#display_relationtype_" +key).val(value['Relationtype']);
$("#display_textrelation_" +key).val(value['TextRelation']);
$("#display_IDRelation_" +key).val(value['IDRelation']);
})
$("#display_relations").val(txt);
// Modal anzeigen
$("#DetailsModal").modal("show");
// Semantisches Netz erstellen (nach Modal sichtbar)
setTimeout(function() {
if (typeof createSemanticNetwork === 'function') {
createSemanticNetwork(row[0].Text, row[0].ID, row[0].Relations);
}
}, 300);
}
}
);
}
function refreshPage()
{
location.reload();
}
function CopyLink(value)
{
var tempInput = document.createElement("input");
tempInput.value = value;
document.body.appendChild(tempInput);
tempInput.select();
document.execCommand("copy");
document.body.removeChild(tempInput);
}

629
js/PersonScript.js Normal file
View File

@ -0,0 +1,629 @@
/**
* Created by Roland on 2021/07/01.
* Updated for BIBB Layout 2024
*/
// PERSONS
// Show blank screen to add new Person
function newPersonShow() {
var b = document.getElementById("newSubjectSave");
b.style.display='inline';
var c = document.getElementById("newSubjectDismiss");
c.style.display='inline';
document.getElementById("SubjectModifySave").style.display = 'none';
document.getElementById("new_term").disabled = false;
document.getElementById("new_scopenote").disabled = false;
// Relationen-Bereich verstecken bei neuer Person
document.getElementById("new_relation_label").style.display = 'none';
document.getElementById("newRelationSubjectDismiss").style.display = 'none';
showRelations(0,0,0);
var a = document.getElementById("relation_table");
if (a != undefined) {
document.getElementById("relation_table").style.display = 'none';
}
var e = document.getElementById("errorNewSubject");
e.style.display = 'none';
$("#new_term").val("");
$("#new_scopenote").val("");
$("#NewSubjectModal").modal("show");
}
// Insert new person record
function CreateNewEntry(type) {
// get values
var term = $("#new_term").val();
term = term.trim();
var detailtype = '';
var classification = '';
var scopenote = $("#new_scopenote").val();
scopenote = scopenote.trim();
if (term.length == 0) {
var a = document.getElementById("errorNewSubject");
a.classList.add('alert-danger');
a.textContent = 'Bitte Name eingeben!';
a.style.display = 'block';
return;
}
var a = document.getElementById("errorNewSubject");
a.classList.remove('alert-danger');
a.textContent = '';
a.style.display = 'none';
$.post("ajax/newPerson.php", {
term : term,
type : type,
detailtype : detailtype,
classification: classification,
scopenote : scopenote
}, function (data, status) {
var dt = (typeof data === 'string') ? JSON.parse(data) : data;
var e = document.getElementById("errorNewSubject");
if (dt['status'] == 200)
{
var AnchorID = dt["Anchor"];
var EntryID = dt["Entry"];
var LinkingID = dt["Linking"];
var b = document.getElementById("newSubjectSave");
b.style.display='none';
var c = document.getElementById("newSubjectDismiss");
c.style.display='none';
document.getElementById("new_term").disabled = true;
document.getElementById("new_scopenote").disabled = true;
$table.bootstrapTable('refresh');
e.classList.remove('alert-danger');
e.classList.add('alert-success');
e.textContent = 'Neuen Namen aufgenommen';
e.style.display = 'block';
// ID setzen für Relationen
$('#ID').val(AnchorID);
// Relationen-Bereich anzeigen nach Speichern
document.getElementById("new_relation_label").style.display = 'block';
document.getElementById("newRelationSubjectDismiss").style.display = 'inline';
showRelations(AnchorID,EntryID,LinkingID);
} else {
e.classList.remove('alert-success');
e.classList.add('alert-danger');
e.textContent = dt['message'];
e.style.display = 'block';
return;
}
});
}
// Show relations of a person record
function showRelations(Anchor,Entry,Linking)
{
$.get("ajax/getRelations.php", {
anchorID:Anchor,
authType:"Person"
},
function (data, status) {
if (status=="success")
{
$(".new_modal_content").html(data);
// Tabelle nur anzeigen wenn Relationen vorhanden
var relationTable = document.getElementById("relation_table");
if (relationTable) {
if (data && data.trim().length > 0) {
relationTable.style.display = 'block';
} else {
relationTable.style.display = 'none';
}
}
$("#NewSubjectModal").modal("show");
}
}
);
}
// create new relation for a person record
function CreateNewRelation(AnchorID, relationID, event) {
if (event) {
event.preventDefault();
event.stopPropagation();
}
console.log('🚀 CreateNewRelation:', { AnchorID, relationID });
try {
// Validierung
if (!AnchorID || !relationID) {
showError('Fehlende Parameter: AnchorID oder relationID');
return false;
}
var relationTypeElement = document.getElementById("new_relationtype");
if (!relationTypeElement) {
showError('Relationstyp-Element nicht gefunden');
return false;
}
var relationType = relationTypeElement.value;
if (!relationType || relationType.trim() === '') {
showError('Bitte wählen Sie einen Relationstyp aus');
return false;
}
// Loading anzeigen
showLoading();
$.ajax({
url: "ajax/writeNewRelation.php",
type: "POST",
data: {
AnchorID: AnchorID,
relationType: relationType,
relationID: relationID
},
dataType: "text",
timeout: 10000,
success: function(response) {
console.log("✅ Success! Response:", response);
try {
var data = JSON.parse(response);
if (data.error) {
showError('<strong>Serverfehler:</strong><br>' + data.error);
return;
}
if (data.success === true) {
showSuccess('Neue Relation erfolgreich aufgenommen!');
if (typeof showRelations === 'function') {
setTimeout(function() {
showRelations(AnchorID, 0, 0);
}, 500);
}
resetForm();
} else {
showError('Unerwartete Antwort vom Server');
}
} catch (parseError) {
console.log("⚠️ Keine JSON-Antwort, prüfe Plain-Text");
if (response && response.length > 0 && !isNaN(response.trim())) {
showSuccess('Neue Relation erfolgreich aufgenommen! (ID: ' + response.trim() + ')');
if (typeof showRelations === 'function') {
setTimeout(function() {
showRelations(AnchorID, 0, 0);
}, 500);
}
resetForm();
} else {
showError('<strong>Unerwartete Antwort:</strong><br>' +
'<code>' + response.substring(0, 200) + '</code>');
}
}
},
error: function(xhr, status, error) {
console.error("❌ AJAX Error:", { status, error, xhr });
var errorMessage = '<strong>Fehler beim Speichern der Relation</strong><br><br>';
if (xhr.status === 0) {
errorMessage += '❌ Keine Verbindung zum Server möglich<br>';
errorMessage += '<small>Prüfen Sie Ihre Internetverbindung</small>';
} else if (xhr.status === 404) {
errorMessage += '❌ Datei nicht gefunden (404)<br>';
errorMessage += '<small>ajax/writeNewRelation.php existiert nicht</small>';
} else if (xhr.status === 500) {
errorMessage += '❌ Interner Serverfehler (500)<br>';
if (xhr.responseText) {
errorMessage += '<br><details><summary>Details anzeigen</summary><pre>' +
xhr.responseText.substring(0, 500) + '</pre></details>';
}
} else if (status === 'timeout') {
errorMessage += '❌ Zeitüberschreitung<br>';
errorMessage += '<small>Der Server hat nicht rechtzeitig geantwortet</small>';
} else if (status === 'parsererror') {
errorMessage += '❌ Fehler beim Verarbeiten der Server-Antwort<br>';
errorMessage += '<small>Die Server-Antwort war ungültig</small>';
} else {
errorMessage += '❌ ' + status + ' (' + xhr.status + ')';
if (error) {
errorMessage += '<br><small>' + error + '</small>';
}
}
showError(errorMessage);
},
complete: function(xhr, status) {
console.log("🏁 Request abgeschlossen:", status);
if (status !== 'success') {
hideLoading();
}
}
});
return false;
} catch (e) {
hideLoading();
console.error("💥 Exception:", e);
showError('<strong>JavaScript-Fehler:</strong><br>' + e.message);
return false;
}
}
function showError(message) {
var errorElement = document.getElementById("errorNewSubject");
if (errorElement) {
errorElement.className = 'alert alert-danger';
errorElement.innerHTML = '<i class="fas fa-exclamation-circle me-2"></i>' + message.replace(/\n/g, '<br>');
errorElement.style.display = 'block';
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
setTimeout(function() {
fadeOutAlert();
}, 8000);
}
}
function showSuccess(message) {
var errorElement = document.getElementById("errorNewSubject");
if (errorElement) {
errorElement.className = 'alert alert-success';
errorElement.innerHTML = '<i class="fas fa-check-circle me-2"></i>' + message;
errorElement.style.display = 'block';
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
setTimeout(function() {
fadeOutAlert();
}, 3000);
}
}
function showWarning(message) {
var errorElement = document.getElementById("errorNewSubject");
if (errorElement) {
errorElement.className = 'alert alert-warning';
errorElement.innerHTML = '<i class="fas fa-exclamation-triangle me-2"></i>' + message;
errorElement.style.display = 'block';
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
setTimeout(function() {
fadeOutAlert();
}, 5000);
}
}
function showLoading() {
var errorElement = document.getElementById("errorNewSubject");
if (errorElement) {
errorElement.className = 'alert alert-info';
errorElement.innerHTML = '<div class="d-flex align-items-center"><div class="spinner-border spinner-border-sm me-3" role="status"><span class="visually-hidden">Laden...</span></div><div>Speichere Relation...</div></div>';
errorElement.style.display = 'block';
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}
function hideLoading() {
var errorElement = document.getElementById("errorNewSubject");
if (errorElement) {
errorElement.style.display = 'none';
}
}
function hideAlert() {
var errorElement = document.getElementById("errorNewSubject");
if (errorElement) {
errorElement.style.display = 'none';
}
}
function fadeOutAlert() {
var errorElement = document.getElementById("errorNewSubject");
if (errorElement && errorElement.style.display !== 'none') {
$(errorElement).fadeOut(500, function() {
errorElement.style.display = 'none';
});
}
}
function resetForm() {
var relationTextElement = document.getElementById("search_relation");
if (relationTextElement) {
relationTextElement.value = '';
}
$('#subject-anlegen').removeData('record-id');
$('#subject-anlegen').removeData('relation-id');
hideAlert();
}
// Delete relation for a person
function DeleteRelation(AnchorID, LinkingID)
{
if (!confirm('Möchten Sie diese Relation wirklich löschen?')) {
return;
}
$.post("ajax/deleteRelation.php", {
AnchorID : AnchorID,
LinkingID : LinkingID
},
function (data, status) {
if (status=="success")
{
var e = document.getElementById("errorNewSubject");
e.classList.add('alert-success');
e.textContent = ' Relation gelöscht';
e.style.display = 'block';
showRelations(AnchorID,0,0)
}
}
);
}
// delete the whole person entry
function DeleteTerm(AnchorID)
{
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
return;
}
$.post("ajax/deleteTerm.php", {
AnchorID: AnchorID[0]
},
function (data, status) {
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
if (status === "success" && metadata.success === true) {
// Tabelle neu laden Zeile ist serverseitig wirklich gelöscht
$table.bootstrapTable('refresh');
} else {
// Serverseitiger Fehler: Zeile wieder einblenden und Meldung anzeigen
$table.bootstrapTable('refresh');
alert('Fehler beim Löschen: ' + (metadata.error || 'Unbekannter Fehler'));
}
}
);
}
// Modify person and its relations
function ModifyPerson(AnchorID)
{
// Name-Feld deaktivieren (darf nicht geändert werden)
document.getElementById("new_term").disabled = true;
// Andere Felder aktivieren
document.getElementById("new_scopenote").disabled = false;
$("#ID").val(AnchorID[0]);
// Relationen-Bereich sofort sichtbar machen bei Bearbeitung!
document.getElementById("new_relation_label").style.display = 'block';
document.getElementById("relation_table").style.display = 'block';
// Buttons konfigurieren
document.getElementById("newSubjectSave").style.display = 'none';
document.getElementById("newSubjectDismiss").style.display = 'inline';
document.getElementById("SubjectModifySave").style.display = 'inline';
document.getElementById("newRelationSubjectDismiss").style.display = 'none';
// Alert verstecken
document.getElementById("errorNewSubject").style.display = 'none';
$("#NewSubjectModalHeadline").text("Person ändern");
$.get("/Thesaurus/ajax/getAuthorityDataRaw.php", {
id : AnchorID[0],
authType :"Person"
},
function (data, status) {
console.log('📥 getAuthorityDataRaw status:', status);
console.log('📥 getAuthorityDataRaw data:', data);
if (status == "success")
{
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
console.log('📥 metadata:', metadata);
dt = (metadata['rows'][0]);
console.log('📥 dt:', dt);
if (dt) {
$("#new_term").val(dt.Text);
$("#new_id").val(dt.ID);
$("#new_descriptor").val(dt.Descriptor);
$("#new_scopenote").val(dt.Scopenote);
console.log('✅ Felder befüllt mit:', dt.Text);
} else {
console.error('❌ Keine Daten in rows[0]');
}
}
}
).fail(function(xhr, status, error) {
console.error('❌ AJAX Fehler:', status, error);
});
$.get("ajax/getRelations.php", {
anchorID:AnchorID[0],
authType: "Person"
},
function (data, status) {
if (status=="success")
{
$(".new_modal_content").html(data);
$("#NewSubjectModal").modal("show");
// Autocomplete neu initialisieren nach Modal-Öffnung
setTimeout(function() {
if (typeof searchRelation === 'function') {
console.log('🔄 Autocomplete wird neu initialisiert...');
searchRelation();
}
}, 300);
}
}
);
}
// rewrite records after updating a person entry
function UpdateEntry(authType)
{
var AnchorID = $("#ID").val();
var term = $("#new_term").val();
term = term.trim();
var type = 'Person';
type = type.trim();
var detailtype = '';
detailtype = detailtype.trim();
var classification = ''
classification = classification.trim();
var scopenote = $("#new_scopenote").val();
scopenote = scopenote.trim();
if (term.length == 0) {
var a = document.getElementById("errorNewSubject");
a.classList.add('alert-danger');
a.textContent = 'Bitte Name eingeben!';
a.style.display = 'block';
return;
}
var a = document.getElementById("errorNewSubject");
a.classList.remove('alert-danger');
a.textContent = '';
a.style.display = 'none';
$.post("ajax/UpdateTerm.php", {
AnchorID: AnchorID,
authType: "Person",
term: term,
type: type,
detailtype: detailtype,
classification: classification,
scopenote: scopenote
},
function (data, status) {
if (status=="success")
{
document.getElementById("relation_table").style.display = 'block'
document.getElementById("newSubjectSave").style.display = 'none'
document.getElementById("newSubjectDismiss").style.display = 'inline'
document.getElementById("SubjectModifySave").style.display = 'none'
document.getElementById("newRelationSubjectDismiss").style.display = 'none'
var e = document.getElementById("errorNewSubject");
e.classList.add('alert-success');
e.textContent = ' Personenname geändert ';
e.style.display = 'block';
$("#NewSubjectModalHeadline").text("Name ändern");
$("#NewSubjectModal").modal("show");
}
}
);
$table.bootstrapTable('refresh')
}
function ShowModalDetails(id) {
$.get("/Thesaurus/ajax/getDetailAuthorityData.php", {
authType : 'Person',
offset : 0,
id : id,
sort : 'Anchor.Text',
limit : 1
},
function(data, status) {
if (status == "success") {
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
var row = metadata['rows'];
if (!row || row.length === 0) {
console.error('❌ Keine Daten gefunden für ID:', id);
return;
}
//Assign existing values to the modal popup fields
$("#display_term").val(row[0].Text);
$("#display_id").val(row[0].ID);
$("#display_id_show").val(row[0].ID);
$("#display_descriptor").val(row[0].Descriptor);
$("#display_type").val(row[0].Type);
$("#display_scopenote").val(row[0].Scopenote);
var html = []
var txt = ''
$.each(row[0].Relations, function (key, value) {
txt = txt +'<div class="col-sm-12">';
txt = txt +'<div class="col-sm-2"></div>';
txt = txt + '<div class="col-sm-3"><input type="text" class="form-control" disabled id="display_relationtype_' +key +'" /></div>';
txt = txt + '<div class="col-sm-5"><input type="text" class="form-control" disabled id="display_textrelation_' +key +'" /></div>';
txt = txt + '<div class="col-sm-2"><input type="text" class="form-control" disabled id="display_IDRelation_' +key +'" /></div>';
txt = txt +'</div>';
})
$(".modal_content").html(txt);
$.each(row[0].Relations, function (key, value) {
$("#display_relationtype_" +key).val(value['Relationtype']);
$("#display_textrelation_" +key).val(value['TextRelation']);
$("#display_IDRelation_" +key).val(value['IDRelation']);
})
$("#display_relations").val(txt);
// Modal anzeigen
$("#DetailsModal").modal("show");
// Semantisches Netz erstellen (nach Modal sichtbar)
setTimeout(function() {
if (typeof createSemanticNetwork === 'function') {
createSemanticNetwork(row[0].Text, row[0].ID, row[0].Relations);
}
}, 300);
}
}
);
}
function refreshPage()
{
location.reload();
}
function CopyLink(value)
{
var tempInput = document.createElement("input");
tempInput.value = value;
document.body.appendChild(tempInput);
tempInput.select();
document.execCommand("copy");
document.body.removeChild(tempInput);
}

634
js/PublisherScript.js Normal file
View File

@ -0,0 +1,634 @@
/**
* Created by Roland on 2021/07/01.
* Updated for BIBB Layout 2024
*/
// PUBLISHERS
// Show blank screen to add new Publisher
function newPublishersShow() {
var b = document.getElementById("newPublisherSave");
b.style.display='inline';
var c = document.getElementById("newPublisherDismiss");
c.style.display='inline';
document.getElementById("PublisherModifySave").style.display = 'none';
document.getElementById("new_term").disabled = false;
document.getElementById("new_type").disabled = false;
document.getElementById("new_scopenote").disabled = false;
// Relationen-Bereich verstecken bei neuem Verlag
document.getElementById("new_relation_label").style.display = 'none';
document.getElementById("newRelationPublisherDismiss").style.display = 'none';
showRelations(0,0,0);
var a = document.getElementById("relation_table");
if (a != undefined) {
document.getElementById("relation_table").style.display = 'none';
}
var e = document.getElementById("errorNewPublisher");
e.style.display = 'none';
$("#new_term").val("");
$("#new_scopenote").val("");
$("#NewPublisherModal").modal("show");
}
// Insert new publisher record
function CreateNewEntry(type) {
// get values
var term = $("#new_term").val();
term = term.trim();
var type = $("#new_type").val();
type = type.trim();
var detailtype = "";
var classification = "";
var scopenote = $("#new_scopenote").val();
scopenote = scopenote.trim();
if (term.length == 0) {
var a = document.getElementById("errorNewPublisher");
a.classList.add('alert-danger');
a.textContent = 'Bitte Verlag eingeben!';
a.style.display = 'block';
return;
}
var a = document.getElementById("errorNewPublisher");
a.classList.remove('alert-danger');
a.textContent = '';
a.style.display = 'none';
$.post("ajax/newPublisher.php", {
term : term,
type : type,
detailtype : detailtype,
classification: classification,
scopenote : scopenote
}, function (data, status) {
var dt = (typeof data === 'string') ? JSON.parse(data) : data;
var e = document.getElementById("errorNewPublisher");
if (dt['status'] == 200)
{
var AnchorID = dt["Anchor"];
var EntryID = dt["Entry"];
var LinkingID = dt["Linking"];
var b = document.getElementById("newPublisherSave");
b.style.display='none';
var c = document.getElementById("newPublisherDismiss");
c.style.display='none';
document.getElementById("new_term").disabled = true;
document.getElementById("new_type").disabled = true;
document.getElementById("new_scopenote").disabled = true;
$table.bootstrapTable('refresh');
e.classList.remove('alert-danger');
e.classList.add('alert-success');
e.textContent = 'Neuen Verlag aufgenommen';
e.style.display = 'block';
// ID setzen für Relationen
$('#ID').val(AnchorID);
// Relationen-Bereich anzeigen nach Speichern
document.getElementById("new_relation_label").style.display = 'block';
document.getElementById("newRelationPublisherDismiss").style.display = 'inline';
showRelations(AnchorID,EntryID,LinkingID);
} else {
e.classList.remove('alert-success');
e.classList.add('alert-danger');
e.textContent = dt['message'];
e.style.display = 'block';
return;
}
});
}
// Show relations of a publisher record
function showRelations(Anchor,Entry,Linking)
{
$.get("ajax/getRelations.php", {
anchorID:Anchor,
authType:"Publisher"
},
function (data, status) {
if (status=="success")
{
$(".new_modal_content").html(data);
// Tabelle nur anzeigen wenn Relationen vorhanden
var relationTable = document.getElementById("relation_table");
if (relationTable) {
if (data && data.trim().length > 0) {
relationTable.style.display = 'block';
} else {
relationTable.style.display = 'none';
}
}
$("#NewPublisherModal").modal("show");
}
}
);
}
// create new relation for a publisher record
function CreateNewRelation(AnchorID, relationID, event) {
if (event) {
event.preventDefault();
event.stopPropagation();
}
console.log('🚀 CreateNewRelation:', { AnchorID, relationID });
try {
// Validierung
if (!AnchorID || !relationID) {
showError('Fehlende Parameter: AnchorID oder relationID');
return false;
}
var relationTypeElement = document.getElementById("new_relationtype");
if (!relationTypeElement) {
showError('Relationstyp-Element nicht gefunden');
return false;
}
var relationType = relationTypeElement.value;
if (!relationType || relationType.trim() === '') {
showError('Bitte wählen Sie einen Relationstyp aus');
return false;
}
// Loading anzeigen
showLoading();
$.ajax({
url: "ajax/writeNewRelation.php",
type: "POST",
data: {
AnchorID: AnchorID,
relationType: relationType,
relationID: relationID
},
dataType: "text",
timeout: 10000,
success: function(response) {
console.log("✅ Success! Response:", response);
try {
var data = JSON.parse(response);
if (data.error) {
showError('<strong>Serverfehler:</strong><br>' + data.error);
return;
}
if (data.success === true) {
showSuccess('Neue Relation erfolgreich aufgenommen!');
if (typeof showRelations === 'function') {
setTimeout(function() {
showRelations(AnchorID, 0, 0);
}, 500);
}
resetForm();
} else {
showError('Unerwartete Antwort vom Server');
}
} catch (parseError) {
console.log("⚠️ Keine JSON-Antwort, prüfe Plain-Text");
if (response && response.length > 0 && !isNaN(response.trim())) {
showSuccess('Neue Relation erfolgreich aufgenommen! (ID: ' + response.trim() + ')');
if (typeof showRelations === 'function') {
setTimeout(function() {
showRelations(AnchorID, 0, 0);
}, 500);
}
resetForm();
} else {
showError('<strong>Unerwartete Antwort:</strong><br>' +
'<code>' + response.substring(0, 200) + '</code>');
}
}
},
error: function(xhr, status, error) {
console.error("❌ AJAX Error:", { status, error, xhr });
var errorMessage = '<strong>Fehler beim Speichern der Relation</strong><br><br>';
if (xhr.status === 0) {
errorMessage += '❌ Keine Verbindung zum Server möglich<br>';
errorMessage += '<small>Prüfen Sie Ihre Internetverbindung</small>';
} else if (xhr.status === 404) {
errorMessage += '❌ Datei nicht gefunden (404)<br>';
errorMessage += '<small>ajax/writeNewRelation.php existiert nicht</small>';
} else if (xhr.status === 500) {
errorMessage += '❌ Interner Serverfehler (500)<br>';
if (xhr.responseText) {
errorMessage += '<br><details><summary>Details anzeigen</summary><pre>' +
xhr.responseText.substring(0, 500) + '</pre></details>';
}
} else if (status === 'timeout') {
errorMessage += '❌ Zeitüberschreitung<br>';
errorMessage += '<small>Der Server hat nicht rechtzeitig geantwortet</small>';
} else if (status === 'parsererror') {
errorMessage += '❌ Fehler beim Verarbeiten der Server-Antwort<br>';
errorMessage += '<small>Die Server-Antwort war ungültig</small>';
} else {
errorMessage += '❌ ' + status + ' (' + xhr.status + ')';
if (error) {
errorMessage += '<br><small>' + error + '</small>';
}
}
showError(errorMessage);
},
complete: function(xhr, status) {
console.log("🏁 Request abgeschlossen:", status);
if (status !== 'success') {
hideLoading();
}
}
});
return false;
} catch (e) {
hideLoading();
console.error("💥 Exception:", e);
showError('<strong>JavaScript-Fehler:</strong><br>' + e.message);
return false;
}
}
function showError(message) {
var errorElement = document.getElementById("errorNewPublisher");
if (errorElement) {
errorElement.className = 'alert alert-danger';
errorElement.innerHTML = '<i class="fas fa-exclamation-circle me-2"></i>' + message.replace(/\n/g, '<br>');
errorElement.style.display = 'block';
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
setTimeout(function() {
fadeOutAlert();
}, 8000);
}
}
function showSuccess(message) {
var errorElement = document.getElementById("errorNewPublisher");
if (errorElement) {
errorElement.className = 'alert alert-success';
errorElement.innerHTML = '<i class="fas fa-check-circle me-2"></i>' + message;
errorElement.style.display = 'block';
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
setTimeout(function() {
fadeOutAlert();
}, 3000);
}
}
function showWarning(message) {
var errorElement = document.getElementById("errorNewPublisher");
if (errorElement) {
errorElement.className = 'alert alert-warning';
errorElement.innerHTML = '<i class="fas fa-exclamation-triangle me-2"></i>' + message;
errorElement.style.display = 'block';
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
setTimeout(function() {
fadeOutAlert();
}, 5000);
}
}
function showLoading() {
var errorElement = document.getElementById("errorNewPublisher");
if (errorElement) {
errorElement.className = 'alert alert-info';
errorElement.innerHTML = '<div class="d-flex align-items-center"><div class="spinner-border spinner-border-sm me-3" role="status"><span class="visually-hidden">Laden...</span></div><div>Speichere Relation...</div></div>';
errorElement.style.display = 'block';
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}
function hideLoading() {
var errorElement = document.getElementById("errorNewPublisher");
if (errorElement) {
errorElement.style.display = 'none';
}
}
function hideAlert() {
var errorElement = document.getElementById("errorNewPublisher");
if (errorElement) {
errorElement.style.display = 'none';
}
}
function fadeOutAlert() {
var errorElement = document.getElementById("errorNewPublisher");
if (errorElement && errorElement.style.display !== 'none') {
$(errorElement).fadeOut(500, function() {
errorElement.style.display = 'none';
});
}
}
function resetForm() {
var relationTextElement = document.getElementById("search_relation");
if (relationTextElement) {
relationTextElement.value = '';
}
$('#publisher-anlegen').removeData('record-id');
$('#publisher-anlegen').removeData('relation-id');
hideAlert();
}
// Delete relation for a publisher
function DeleteRelation(AnchorID, LinkingID)
{
if (!confirm('Möchten Sie diese Relation wirklich löschen?')) {
return;
}
$.post("ajax/deleteRelation.php", {
AnchorID : AnchorID,
LinkingID : LinkingID
},
function (data, status) {
if (status=="success")
{
var e = document.getElementById("errorNewPublisher");
e.classList.add('alert-success');
e.textContent = ' Relation gelöscht';
e.style.display = 'block';
showRelations(AnchorID,0,0)
}
}
);
}
// delete the whole publisher entry
function DeleteTerm(AnchorID)
{
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
return;
}
$.post("ajax/deleteTerm.php", {
AnchorID: AnchorID[0]
},
function (data, status) {
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
if (status === "success" && metadata.success === true) {
// Tabelle neu laden Zeile ist serverseitig wirklich gelöscht
$table.bootstrapTable('refresh');
} else {
// Serverseitiger Fehler: Zeile wieder einblenden und Meldung anzeigen
$table.bootstrapTable('refresh');
alert('Fehler beim Löschen: ' + (metadata.error || 'Unbekannter Fehler'));
}
}
);
}
// Modify publisher and its relations
function ModifyTerm(AnchorID)
{
// Name-Feld deaktivieren (darf nicht geändert werden)
document.getElementById("new_term").disabled = true;
// Andere Felder aktivieren
document.getElementById("new_type").disabled = false;
document.getElementById("new_scopenote").disabled = false;
$("#ID").val(AnchorID[0]);
// Relationen-Bereich sofort sichtbar machen bei Bearbeitung!
document.getElementById("new_relation_label").style.display = 'block';
document.getElementById("relation_table").style.display = 'block';
// Buttons konfigurieren
document.getElementById("newPublisherSave").style.display = 'none';
document.getElementById("newPublisherDismiss").style.display = 'inline';
document.getElementById("PublisherModifySave").style.display = 'inline';
document.getElementById("newRelationPublisherDismiss").style.display = 'none';
// Alert verstecken
document.getElementById("errorNewPublisher").style.display = 'none';
$("#NewPublisherModalHeadline").text("Verlag ändern");
$.get("/Thesaurus/ajax/getAuthorityDataRaw.php", {
id : AnchorID[0],
authType :"Publisher"
},
function (data, status) {
console.log('📥 getAuthorityDataRaw status:', status);
console.log('📥 getAuthorityDataRaw data:', data);
if (status == "success")
{
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
console.log('📥 metadata:', metadata);
dt = (metadata['rows'][0]);
console.log('📥 dt:', dt);
if (dt) {
$("#new_term").val(dt.Text);
$("#new_id").val(dt.ID);
$("#new_descriptor").val(dt.Descriptor);
$("#new_type").val(dt.Type);
$("#new_scopenote").val(dt.Scopenote);
console.log('✅ Felder befüllt mit:', dt.Text);
} else {
console.error('❌ Keine Daten in rows[0]');
}
}
}
).fail(function(xhr, status, error) {
console.error('❌ AJAX Fehler:', status, error);
});
$.get("ajax/getRelations.php", {
anchorID:AnchorID[0],
authType: "Publisher"
},
function (data, status) {
if (status=="success")
{
$(".new_modal_content").html(data);
$("#NewPublisherModal").modal("show");
// Autocomplete neu initialisieren nach Modal-Öffnung
setTimeout(function() {
if (typeof searchRelation === 'function') {
console.log('🔄 Autocomplete wird neu initialisiert...');
searchRelation();
}
}, 300);
}
}
);
}
// rewrite records after updating a publisher entry
function UpdateEntry(authType)
{
var AnchorID = $("#ID").val();
var term = $("#new_term").val();
term = term.trim();
var type = $("#new_type").val();
type = type.trim();
var detailtype = '';
var classification = '';
var scopenote = $("#new_scopenote").val();
scopenote = scopenote.trim();
if (term.length == 0) {
var a = document.getElementById("errorNewPublisher");
a.classList.add('alert-danger');
a.textContent = 'Bitte Verlag eingeben!';
a.style.display = 'block';
return;
}
var a = document.getElementById("errorNewPublisher");
a.classList.remove('alert-danger');
a.textContent = '';
a.style.display = 'none';
$.post("ajax/UpdateTerm.php", {
AnchorID: AnchorID,
authType: "Publisher",
term: term,
type: type,
detailtype: detailtype,
classification: classification,
scopenote: scopenote
},
function (data, status) {
if (status=="success")
{
document.getElementById("relation_table").style.display = 'block'
document.getElementById("newPublisherSave").style.display = 'none'
document.getElementById("newPublisherDismiss").style.display = 'inline'
document.getElementById("PublisherModifySave").style.display = 'none'
document.getElementById("newRelationPublisherDismiss").style.display = 'none'
var e = document.getElementById("errorNewPublisher");
e.classList.add('alert-success');
e.textContent = ' Verlag geändert ';
e.style.display = 'block';
$("#NewPublisherModalHeadline").text("Verlag ändern");
$("#NewPublisherModal").modal("show");
}
}
);
$table.bootstrapTable('refresh')
}
function ShowModalDetails(id) {
$.get("/Thesaurus/ajax/getDetailAuthorityData.php", {
authType : 'Publisher',
offset : 0,
id : id,
sort : 'Anchor.Text',
limit : 1
},
function(data, status) {
if (status == "success") {
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
var row = metadata['rows'];
if (!row || row.length === 0) {
console.error('❌ Keine Daten gefunden für ID:', id);
return;
}
//Assign existing values to the modal popup fields
$("#display_term").val(row[0].Text);
$("#display_id").val(row[0].ID);
$("#display_id_show").val(row[0].ID);
$("#display_descriptor").val(row[0].Descriptor);
$("#display_type").val(row[0].Type);
$("#display_scopenote").val(row[0].Scopenote);
var html = []
var txt = ''
$.each(row[0].Relations, function (key, value) {
txt = txt +'<div class="col-sm-12">';
txt = txt +'<div class="col-sm-2"></div>';
txt = txt + '<div class="col-sm-3"><input type="text" class="form-control" disabled id="display_relationtype_' +key +'" /></div>';
txt = txt + '<div class="col-sm-5"><input type="text" class="form-control" disabled id="display_textrelation_' +key +'" /></div>';
txt = txt + '<div class="col-sm-2"><input type="text" class="form-control" disabled id="display_IDRelation_' +key +'" /></div>';
txt = txt +'</div>';
})
$(".modal_content").html(txt);
$.each(row[0].Relations, function (key, value) {
$("#display_relationtype_" +key).val(value['Relationtype']);
$("#display_textrelation_" +key).val(value['TextRelation']);
$("#display_IDRelation_" +key).val(value['IDRelation']);
})
$("#display_relations").val(txt);
// Modal anzeigen
$("#DetailsModal").modal("show");
// Semantisches Netz erstellen (nach Modal sichtbar)
setTimeout(function() {
if (typeof createSemanticNetwork === 'function') {
createSemanticNetwork(row[0].Text, row[0].ID, row[0].Relations);
}
}, 300);
}
}
);
}
function refreshPage()
{
location.reload();
}
function CopyLink(value)
{
var tempInput = document.createElement("input");
var words = value.split('(');
tempInput.value = words[0];
document.body.appendChild(tempInput);
tempInput.select();
document.execCommand("copy");
document.body.removeChild(tempInput);
}

882
js/SubjectScript.js Normal file
View File

@ -0,0 +1,882 @@
/**
* Created by Roland on 2021/07/01.
*/
// SUBJECTS
// Show blank screen to add new Subject
function newSubjectShow() {
var b = document.getElementById("newSubjectSave");
b.style.display='inline';
var c = document.getElementById("newSubjectDismiss");
c.style.display='inline';
document.getElementById("SubjectModifySave").style.display = 'none'
document.getElementById("new_term").disabled = false;
document.getElementById("new_type").disabled = false;
document.getElementById("new_detailtype").disabled = false;
document.getElementById("new_classification").disabled = false;
document.getElementById("new_scopenote").disabled = false;
// Relationen-Bereich verstecken bei neuem Schlagwort
document.getElementById("new_relation_label").style.display = 'none';
document.getElementById("newRelationSubjectDismiss").style.display = 'none';
// Synonyme-Bereich verstecken bei neuem Schlagwort
var synSection = document.getElementById("synonyms_section");
if (synSection) {
synSection.style.display = 'none';
var tbody = document.getElementById("synonyms_body");
if (tbody) tbody.innerHTML = '';
}
showRelations(0,0,0);
var a = document.getElementById("relation_table");
if (a != undefined) {
document.getElementById("relation_table").style.display = 'none';
}
var e = document.getElementById("errorNewSubject");
e.style.display = 'none';
$("#new_term").val("");
$("#new_classification").val("");
$("#new_scopenote").val("");
$("#NewSubjectModal").modal("show");
}
// Insert new subject record
function CreateNewEntry(type) {
// get values
var term = $("#new_term").val();
term = term.trim();
var type = $("#new_type").val();
type = type.trim();
var detailtype = $("#new_detailtype").val();
detailtype = detailtype.trim();
var classification = $("#new_classification").val();
classification = classification.trim();
var scopenote = $("#new_scopenote").val();
scopenote = scopenote.trim();
if (term.length == 0) {
var a = document.getElementById("errorNewSubject");
a.classList.add('alert-danger');
a.textContent = 'Bitte Schlagwort eingeben!';
a.style.display = 'block';
return;
}
var a = document.getElementById("errorNewSubject");
a.classList.remove('alert-danger');
a.textContent = '';
a.style.display = 'none';
$.post("ajax/newSubject.php", {
term : term,
type : type,
detailtype : detailtype,
classification: classification,
scopenote : scopenote
}, function (data, status) {
var dt = (typeof data === 'string') ? JSON.parse(data) : data;
var e = document.getElementById("errorNewSubject");
if (dt['status'] == 200)
{
var AnchorID = dt["Anchor"];
var EntryID = dt["Entry"];
var LinkingID = dt["Linking"];
// alert ("ANCHOR: " +AnchorID);
var b = document.getElementById("newSubjectSave");
b.style.display='none';
var c = document.getElementById("newSubjectDismiss");
c.style.display='none';
document.getElementById("new_term").disabled = true;
document.getElementById("new_type").disabled = true;
document.getElementById("new_detailtype").disabled = true;
document.getElementById("new_classification").disabled = true;
document.getElementById("new_scopenote").disabled = true;
$table.bootstrapTable('refresh');
e.classList.remove('alert-danger');
e.classList.add('alert-success');
e.textContent = 'Neues Schlagwort aufgenommen';
e.style.display = 'block';
// ID setzen für Relationen
$('#ID').val(AnchorID);
// Relationen-Bereich anzeigen nach Speichern
document.getElementById("new_relation_label").style.display = 'block';
document.getElementById("newRelationSubjectDismiss").style.display = 'inline';
showRelations(AnchorID,EntryID,LinkingID);
} else {
e.classList.remove('alert-success');
e.classList.add('alert-danger');
e.textContent = dt['message'];
e.style.display = 'block';
return;
}
});
}
// Show relations of a subect record
function showRelations(Anchor,Entry,Linking)
{
// alert ("showRelations AnchorID : " + Anchor);
$.get("ajax/getRelations.php", {
anchorID:Anchor,
authType: "Subject"
},
function (data, status) {
if (status=="success")
{
$(".new_modal_content").html(data);
// Tabelle nur anzeigen wenn Relationen vorhanden
var relationTable = document.getElementById("relation_table");
if (relationTable) {
if (data && data.trim().length > 0) {
relationTable.style.display = 'block';
} else {
relationTable.style.display = 'none';
}
}
$("#NewSubjectModal").modal("show");
}
}
);
}
// create new relation for a subject record
function CreateNewRelation(AnchorID, relationID, event) {
if (event) {
event.preventDefault();
event.stopPropagation();
}
console.log('🚀 CreateNewRelation:', { AnchorID, relationID });
try {
// Validierung
if (!AnchorID || !relationID) {
showError('Fehlende Parameter: AnchorID oder relationID');
return false;
}
var relationTypeElement = document.getElementById("new_relationtype");
if (!relationTypeElement) {
showError('Relationstyp-Element nicht gefunden');
return false;
}
var relationType = relationTypeElement.value;
if (!relationType || relationType.trim() === '') {
showError('Bitte wählen Sie einen Relationstyp aus');
return false;
}
// Loading anzeigen
showLoading();
$.ajax({
url: "ajax/writeNewRelation.php",
type: "POST",
data: {
AnchorID: AnchorID,
relationType: relationType,
relationID: relationID
},
dataType: "text",
timeout: 10000,
success: function(response) {
console.log("✅ Success! Response:", response);
try {
var data = JSON.parse(response);
if (data.error) {
showError('<strong>Serverfehler:</strong><br>' + data.error);
return;
}
if (data.success === true) {
showSuccess('Neue Relation erfolgreich aufgenommen!');
// Relations neu laden nach kurzer Verzögerung
if (typeof showRelations === 'function') {
setTimeout(function() {
showRelations(AnchorID, 0, 0);
}, 500);
}
resetForm();
} else {
showError('Unerwartete Antwort vom Server');
}
} catch (parseError) {
console.log("⚠️ Keine JSON-Antwort, prüfe Plain-Text");
// Fallback für alte API (nur ID zurück)
if (response && response.length > 0 && !isNaN(response.trim())) {
showSuccess('Neue Relation erfolgreich aufgenommen! (ID: ' + response.trim() + ')');
if (typeof showRelations === 'function') {
setTimeout(function() {
showRelations(AnchorID, 0, 0);
}, 500);
}
resetForm();
} else {
showError('<strong>Unerwartete Antwort:</strong><br>' +
'<code>' + response.substring(0, 200) + '</code>');
}
}
},
error: function(xhr, status, error) {
console.error("❌ AJAX Error:", { status, error, xhr });
var errorMessage = '<strong>Fehler beim Speichern der Relation</strong><br><br>';
if (xhr.status === 0) {
errorMessage += '❌ Keine Verbindung zum Server möglich<br>';
errorMessage += '<small>Prüfen Sie Ihre Internetverbindung</small>';
} else if (xhr.status === 404) {
errorMessage += '❌ Datei nicht gefunden (404)<br>';
errorMessage += '<small>ajax/writeNewRelation.php existiert nicht</small>';
} else if (xhr.status === 500) {
errorMessage += '❌ Interner Serverfehler (500)<br>';
if (xhr.responseText) {
errorMessage += '<br><details><summary>Details anzeigen</summary><pre>' +
xhr.responseText.substring(0, 500) + '</pre></details>';
}
} else if (status === 'timeout') {
errorMessage += '❌ Zeitüberschreitung<br>';
errorMessage += '<small>Der Server hat nicht rechtzeitig geantwortet</small>';
} else if (status === 'parsererror') {
errorMessage += '❌ Fehler beim Verarbeiten der Server-Antwort<br>';
errorMessage += '<small>Die Server-Antwort war ungültig</small>';
} else {
errorMessage += '❌ ' + status + ' (' + xhr.status + ')';
if (error) {
errorMessage += '<br><small>' + error + '</small>';
}
}
showError(errorMessage);
},
complete: function(xhr, status) {
console.log("🏁 Request abgeschlossen:", status);
// Loading nur ausblenden wenn kein Erfolg (Success zeigt eigene Meldung)
if (status !== 'success') {
hideLoading();
}
}
});
return false;
} catch (e) {
hideLoading();
console.error("💥 Exception:", e);
showError('<strong>JavaScript-Fehler:</strong><br>' + e.message);
return false;
}
}
// Neue Funktion für Synonyme
function CreateNewSynonym(text1, text2, anchorID, event) {
if (event) {
event.preventDefault();
event.stopPropagation();
}
console.log('🔗 CreateNewSynonym:', { text1, text2 });
try {
// Validierung
if (!text1 || !text2) {
showError('Fehlende Parameter: text1 oder text2');
return false;
}
if (text1.trim() === text2.trim()) {
showError('Ein Begriff kann nicht sein eigenes Synonym sein');
return false;
}
// Loading anzeigen
showLoading();
$.ajax({
url: "/Thesaurus/ajax/saveSynonym.php",
type: "POST",
data: {
text1: text1,
text2: text2,
action: 'add'
},
dataType: "json",
timeout: 10000,
success: function(response) {
console.log("✅ Synonym Success! Response:", response);
if (response.error) {
showError('<strong>Fehler:</strong><br>' + response.error);
return;
}
if (response.success === true) {
showSuccess('Synonym erfolgreich hinzugefügt!');
// Synonyme neu laden
var termText = $('#new_term').val();
if (termText) {
loadSynonyms(termText);
}
// Relations neu laden nach kurzer Verzögerung
if (typeof showRelations === 'function' && anchorID) {
setTimeout(function() {
showRelations(anchorID, 0, 0);
}, 500);
}
resetForm();
} else {
showError('Unerwartete Antwort vom Server');
}
},
error: function(xhr, status, error) {
console.error("❌ AJAX Error:", { status, error, xhr });
var errorMessage = '<strong>Fehler beim Speichern des Synonyms</strong><br><br>';
if (xhr.status === 0) {
errorMessage += '❌ Keine Verbindung zum Server möglich';
} else if (xhr.status === 404) {
errorMessage += '❌ Datei nicht gefunden (404)<br>';
errorMessage += '<small>ajax/saveSynonym.php existiert nicht</small>';
} else if (xhr.status === 500) {
errorMessage += '❌ Interner Serverfehler (500)';
} else {
errorMessage += '❌ ' + status + ' (' + xhr.status + ')';
}
showError(errorMessage);
},
complete: function() {
hideLoading();
}
});
return false;
} catch (e) {
hideLoading();
console.error("💥 Exception:", e);
showError('<strong>JavaScript-Fehler:</strong><br>' + e.message);
return false;
}
}
// Synonyme für einen Begriff laden
function loadSynonyms(text) {
if (!text) return;
console.log('📋 Lade Synonyme für:', text);
$.ajax({
url: "/Thesaurus/ajax/getSynonyms.php",
type: "GET",
data: { text: text },
dataType: "json",
success: function(response) {
console.log('✅ Synonyme geladen:', response);
var section = document.getElementById('synonyms_section');
var tbody = document.getElementById('synonyms_body');
var noSyn = document.getElementById('no_synonyms');
if (response.success && response.count > 0) {
// Synonyme vorhanden - Bereich anzeigen
if (section) section.style.display = 'block';
if (tbody) tbody.innerHTML = response.html;
if (noSyn) noSyn.style.display = 'none';
} else {
// Keine Synonyme - Bereich komplett ausblenden
if (section) section.style.display = 'none';
if (tbody) tbody.innerHTML = '';
}
},
error: function(xhr, status, error) {
console.error('❌ Fehler beim Laden der Synonyme:', error);
}
});
}
// Synonym löschen
function DeleteSynonym(text1, text2) {
if (!confirm('Möchten Sie das Synonym "' + text2 + '" wirklich entfernen?')) {
return;
}
console.log('🗑️ Lösche Synonym:', { text1, text2 });
$.ajax({
url: "/Thesaurus/ajax/saveSynonym.php",
type: "POST",
data: {
text1: text1,
text2: text2,
action: 'remove'
},
dataType: "json",
success: function(response) {
console.log('✅ Synonym gelöscht:', response);
if (response.success) {
showSuccess('Synonym entfernt!');
// Synonyme neu laden
loadSynonyms(text1);
} else {
showError(response.error || 'Fehler beim Löschen');
}
},
error: function(xhr, status, error) {
console.error('❌ Fehler beim Löschen:', error);
showError('Fehler beim Löschen des Synonyms');
}
});
}
function showError(message) {
var errorElement = document.getElementById("errorNewSubject");
if (errorElement) {
errorElement.className = 'alert alert-danger';
errorElement.innerHTML = '<i class="fas fa-exclamation-circle me-2"></i>' + message.replace(/\n/g, '<br>');
errorElement.style.display = 'block';
// Scroll zum Alert
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
// Optional: Auto-Hide nach 8 Sekunden
setTimeout(function() {
fadeOutAlert();
}, 8000);
}
}
function showSuccess(message) {
var errorElement = document.getElementById("errorNewSubject");
if (errorElement) {
errorElement.className = 'alert alert-success';
errorElement.innerHTML = '<i class="fas fa-check-circle me-2"></i>' + message;
errorElement.style.display = 'block';
// Scroll zum Alert
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
// Auto-Hide nach 3 Sekunden
setTimeout(function() {
fadeOutAlert();
}, 3000);
}
}
function showWarning(message) {
var errorElement = document.getElementById("errorNewSubject");
if (errorElement) {
errorElement.className = 'alert alert-warning';
errorElement.innerHTML = '<i class="fas fa-exclamation-triangle me-2"></i>' + message;
errorElement.style.display = 'block';
// Scroll zum Alert
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
// Auto-Hide nach 5 Sekunden
setTimeout(function() {
fadeOutAlert();
}, 5000);
}
}
function showLoading() {
var errorElement = document.getElementById("errorNewSubject");
if (errorElement) {
errorElement.className = 'alert alert-info';
errorElement.innerHTML = '<div class="d-flex align-items-center"><div class="spinner-border spinner-border-sm me-3" role="status"><span class="visually-hidden">Laden...</span></div><div>Speichere Relation...</div></div>';
errorElement.style.display = 'block';
// Scroll zum Alert
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}
function hideLoading() {
var errorElement = document.getElementById("errorNewSubject");
if (errorElement) {
errorElement.style.display = 'none';
}
}
function hideAlert() {
var errorElement = document.getElementById("errorNewSubject");
if (errorElement) {
errorElement.style.display = 'none';
}
}
function fadeOutAlert() {
var errorElement = document.getElementById("errorNewSubject");
if (errorElement && errorElement.style.display !== 'none') {
$(errorElement).fadeOut(500, function() {
errorElement.style.display = 'none';
});
}
}
function resetForm() {
var relationTextElement = document.getElementById("search_relation");
if (relationTextElement) {
relationTextElement.value = '';
}
// Data-Attribute zurücksetzen
$('#subject-anlegen').removeData('record-id');
$('#subject-anlegen').removeData('record-text');
$('#subject-anlegen').removeData('relation-id');
$('#subject-anlegen').removeData('relation-text');
hideAlert();
}
// Delete relation for a subjects
function DeleteRelation(AnchorID, LinkingID)
{
if (!confirm('Möchten Sie diese Relation wirklich löschen?')) {
return;
}
$.post("ajax/deleteRelation.php", {
AnchorID : AnchorID,
LinkingID : LinkingID
},
function (data, status) {
if (status=="success")
{
var e = document.getElementById("errorNewSubject");
e.classList.add('alert-success');
e.textContent = ' Relation gelöscht';
e.style.display = 'block';
showRelations(AnchorID,0,0)
}
}
);
}
// delete the whole subject entry
function DeleteTerm(AnchorID)
{
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
return;
}
$.post("ajax/deleteTerm.php", {
AnchorID: AnchorID[0]
},
function (data, status) {
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
if (status === "success" && metadata.success === true) {
// Tabelle neu laden Zeile ist serverseitig wirklich gelöscht
$table.bootstrapTable('refresh');
} else {
// Serverseitiger Fehler: Zeile wieder einblenden und Meldung anzeigen
$table.bootstrapTable('refresh');
alert('Fehler beim Löschen: ' + (metadata.error || 'Unbekannter Fehler'));
}
}
);
}
// Modify subject and its relations
function ModifyTerm(AnchorID)
{
// Schlagwort-Feld deaktivieren (darf nicht geändert werden)
document.getElementById("new_term").disabled = true;
// Andere Felder aktivieren
document.getElementById("new_type").disabled = false;
document.getElementById("new_detailtype").disabled = false;
document.getElementById("new_classification").disabled = false;
document.getElementById("new_scopenote").disabled = false;
$("#ID").val(AnchorID[0]);
// Relationen-Bereich sofort sichtbar machen bei Bearbeitung!
document.getElementById("new_relation_label").style.display = 'block';
document.getElementById("relation_table").style.display = 'block';
// Synonyme-Bereich sichtbar machen
var synSection = document.getElementById("synonyms_section");
if (synSection) synSection.style.display = 'block';
// Buttons konfigurieren
document.getElementById("newSubjectSave").style.display = 'none';
document.getElementById("newSubjectDismiss").style.display = 'inline';
document.getElementById("SubjectModifySave").style.display = 'inline';
document.getElementById("newRelationSubjectDismiss").style.display = 'none';
// Alert verstecken
document.getElementById("errorNewSubject").style.display = 'none';
$("#NewSubjectModalHeadline").text("Schlagwort ändern");
$.get("/Thesaurus/ajax/getAuthorityDataRaw.php", {
id : AnchorID[0]
},
function (data, status) {
console.log('📥 getAuthorityDataRaw status:', status);
console.log('📥 getAuthorityDataRaw data:', data);
if (status == "success")
{
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
console.log('📥 metadata:', metadata);
dt = (metadata['rows'][0]);
console.log('📥 dt:', dt);
if (dt) {
$("#new_term").val(dt.Text);
$("#new_id").val(dt.ID);
$("#new_detailtype").val(dt.DetailType);
$("#new_classification").val(dt.Classification);
$("#new_descriptor").val(dt.Descriptor);
$("#new_type").val(dt.Type);
$("#new_scopenote").val(dt.Scopenote);
console.log('✅ Felder befüllt mit:', dt.Text);
// Synonyme laden
if (typeof loadSynonyms === 'function') {
loadSynonyms(dt.Text);
}
} else {
console.error('❌ Keine Daten in rows[0]');
}
}
}
).fail(function(xhr, status, error) {
console.error('❌ AJAX Fehler:', status, error);
});
$.get("ajax/getRelations.php", {
anchorID:AnchorID[0],
authType: "Subject"
},
function (data, status) {
if (status=="success")
{
$(".new_modal_content").html(data);
$("#NewSubjectModal").modal("show");
// Autocomplete neu initialisieren nach Modal-Öffnung
setTimeout(function() {
if (typeof searchRelation === 'function') {
console.log('🔄 Autocomplete wird neu initialisiert...');
searchRelation();
}
}, 300);
}
}
);
}
// rewrite records after updating a subject entry
function UpdateEntry(authType)
{
var AnchorID = $("#ID").val();
var term = $("#new_term").val();
term = term.trim();
var type = $("#new_type").val();
type = type.trim();
var detailtype = $("#new_detailtype").val();
detailtype = detailtype.trim();
var classification = $("#new_classification").val();
classification = classification.trim();
var scopenote = $("#new_scopenote").val();
scopenote = scopenote.trim();
if (term.length == 0) {
var a = document.getElementById("errorNewSubject");
a.classList.add('alert-danger');
a.textContent = 'Bitte Schlagwort eingeben!';
a.style.display = 'block';
return;
}
var a = document.getElementById("errorNewSubject");
a.classList.remove('alert-danger');
a.textContent = '';
a.style.display = 'none';
$.post("ajax/UpdateTerm.php", {
AnchorID: AnchorID,
authType: authType,
term: term,
type: type,
detailtype: detailtype,
classification: classification,
scopenote: scopenote
},
function (data, status) {
if (status=="success")
{
document.getElementById("relation_table").style.display = 'block'
document.getElementById("newSubjectSave").style.display = 'none'
document.getElementById("newSubjectDismiss").style.display = 'inline'
document.getElementById("SubjectModifySave").style.display = 'none'
document.getElementById("newRelationSubjectDismiss").style.display = 'none'
var e = document.getElementById("errorNewSubject");
e.classList.add('alert-success');
e.textContent = ' Schlagwort geändert ';
e.style.display = 'block';
$("#NewSubjectModalHeadline").text("Schlagwort ändern");
$("#NewSubjectModal").modal("show");
}
}
);
$table.bootstrapTable('refresh')
}
function ShowModalDetails(id) {
$.get("/Thesaurus/ajax/getDetailAuthorityData.php", {
authType : 'Subject',
offset : 0,
id : id,
sort : 'Anchor.Text',
limit : 1
},
function(data, status) {
if (status == "success") {
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
var row = metadata['rows'];
if (!row || row.length === 0) {
console.error('❌ Keine Daten gefunden für ID:', id);
return;
}
//Assign existing values to the modal popup fields
$("#display_term").val(row[0].Text);
$("#display_id").val(row[0].ID);
$("#display_id_show").val(row[0].ID);
$("#display_detailtype").val(row[0].Detailtype);
$("#display_classification").val(row[0].Classification);
$("#display_descriptor").val(row[0].Descriptor);
$("#display_type").val(row[0].Type);
$("#display_scopenote").val(row[0].Scopenote);
$("#display_synonyms").val(row[0].Synonyms || '');
// Alte tabellarische Darstellung (versteckt)
var html = []
var txt = ''
$.each(row[0].Relations, function (key, value) {
txt = txt +'<div class="col-sm-12">';
txt = txt +'<div class="col-sm-2"></div>';
txt = txt + '<div class="col-sm-3"><input type="text" class="form-control" disabled id="display_relationtype_' +key +'" /></div>';
txt = txt + '<div class="col-sm-5"><input type="text" class="form-control" disabled id="display_textrelation_' +key +'" /></div>';
txt = txt + '<div class="col-sm-2"><input type="text" class="form-control" disabled id="display_IDRelation_' +key +'" /></div>';
txt = txt +'</div>';
})
$(".modal_content").html(txt);
$.each(row[0].Relations, function (key, value) {
$("#display_relationtype_" +key).val(value['Relationtype']);
$("#display_textrelation_" +key).val(value['TextRelation']);
$("#display_IDRelation_" +key).val(value['IDRelation']);
})
$("#display_relations").val(txt);
// Modal anzeigen
$("#DetailsModal").modal("show");
// Semantisches Netz erstellen (nach Modal sichtbar)
setTimeout(function() {
if (typeof createSemanticNetwork === 'function') {
var synonyms = row[0].Synonyms || '';
// Falls keine Synonyme im Response, versuche sie separat zu laden
if (!synonyms || synonyms.trim() === '') {
console.log('📝 Keine Synonyme im Response, lade separat...');
$.ajax({
url: "/Thesaurus/ajax/getSynonyms.php",
type: "GET",
data: { text: row[0].Text },
dataType: "json",
async: false, // Synchron um auf Ergebnis zu warten
success: function(synResponse) {
if (synResponse.success && synResponse.synonyms && synResponse.synonyms.length > 0) {
synonyms = synResponse.synonyms.map(function(s) { return s.text; }).join(', ');
console.log('✅ Synonyme separat geladen:', synonyms);
// Auch das Textfeld aktualisieren
$("#display_synonyms").val(synonyms);
}
}
});
}
createSemanticNetwork(row[0].Text, row[0].ID, row[0].Relations, synonyms);
} else {
console.warn('⚠️ createSemanticNetwork Funktion nicht gefunden');
}
}, 300);
}
}
);
}
function CopyLink(value)
{
var tempInput = document.createElement("input");
tempInput.value = value;
document.body.appendChild(tempInput);
tempInput.select();
document.execCommand("copy");
document.body.removeChild(tempInput);
}
function refreshPage()
{
location.reload();
}

305
migrate_tsdata.sh Executable file
View File

@ -0,0 +1,305 @@
#!/bin/bash
#
# TSData Migration Script
# Migriert Daten von DSpace (MySQL 5.5) nach Tools (MariaDB 10.5)
#
# Verwendung:
# 1. Skript auf DSpace ausführen: ./migrate_tsdata.sh export
# 2. SQL-Datei auf Tools kopieren
# 3. Skript auf Tools ausführen: ./migrate_tsdata.sh import /pfad/zur/datei.sql
#
set -e
# Konfiguration
DB_NAME="TSData"
DB_USER="root"
BACKUP_DIR="/tmp/tsdata_migration"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
EXPORT_FILE="${BACKUP_DIR}/TSData_export_${TIMESTAMP}.sql"
# Farben für Output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Verzeichnis erstellen
mkdir -p "$BACKUP_DIR"
#######################################
# EXPORT (auf DSpace ausführen)
#######################################
do_export() {
log_info "Starte Export auf DSpace..."
# Prüfen ob Datenbank existiert
if ! mysql -u "$DB_USER" -p -e "USE $DB_NAME" 2>/dev/null; then
log_error "Datenbank $DB_NAME nicht gefunden!"
exit 1
fi
log_info "Erstelle Daten-Export (ohne Struktur)..."
# Export mit vollständigen INSERT-Statements
mysqldump -u "$DB_USER" -p "$DB_NAME" \
--no-create-info \
--complete-insert \
--skip-triggers \
--single-transaction \
--quick \
--lock-tables=false \
--set-charset \
--default-character-set=utf8 \
> "$EXPORT_FILE"
# Wrapper für SQL-Mode hinzufügen
TEMP_FILE="${EXPORT_FILE}.tmp"
cat > "$TEMP_FILE" << 'HEADER'
-- TSData Migration Export
-- Erstellt am: TIMESTAMP_PLACEHOLDER
-- Quelle: DSpace Server
SET @OLD_SQL_MODE=@@SQL_MODE;
SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO,ALLOW_INVALID_DATES';
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS;
SET FOREIGN_KEY_CHECKS=0;
SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS;
SET UNIQUE_CHECKS=0;
-- DateModified '0000-00-00' Werte korrigieren (für MariaDB Strict Mode)
-- Dies geschieht nach dem Import
HEADER
sed -i "s/TIMESTAMP_PLACEHOLDER/$(date)/" "$TEMP_FILE"
cat "$EXPORT_FILE" >> "$TEMP_FILE"
cat >> "$TEMP_FILE" << 'FOOTER'
SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;
-- Export Ende
FOOTER
mv "$TEMP_FILE" "$EXPORT_FILE"
# Statistiken anzeigen
log_info "Export abgeschlossen: $EXPORT_FILE"
log_info "Dateigröße: $(du -h "$EXPORT_FILE" | cut -f1)"
echo ""
log_info "Datensätze pro Tabelle:"
mysql -u "$DB_USER" -p -N -e "
SELECT 'Anchor', COUNT(*) FROM $DB_NAME.Anchor
UNION ALL SELECT 'Entry', COUNT(*) FROM $DB_NAME.Entry
UNION ALL SELECT 'EntryNew', COUNT(*) FROM $DB_NAME.EntryNew
UNION ALL SELECT 'IDSynonym', COUNT(*) FROM $DB_NAME.IDSynonym
UNION ALL SELECT 'Linking', COUNT(*) FROM $DB_NAME.Linking
UNION ALL SELECT 'Synonyms', COUNT(*) FROM $DB_NAME.Synonyms
UNION ALL SELECT 'Treeview', COUNT(*) FROM $DB_NAME.Treeview;
" 2>/dev/null | column -t
echo ""
log_info "Nächster Schritt: Datei auf Tools-Server kopieren:"
echo " scp $EXPORT_FILE user@tools.bibb.de:/tmp/"
}
#######################################
# IMPORT (auf Tools ausführen)
#######################################
do_import() {
IMPORT_FILE="$1"
if [ -z "$IMPORT_FILE" ]; then
log_error "Keine Import-Datei angegeben!"
echo "Verwendung: $0 import /pfad/zur/export_datei.sql"
exit 1
fi
if [ ! -f "$IMPORT_FILE" ]; then
log_error "Datei nicht gefunden: $IMPORT_FILE"
exit 1
fi
log_info "Starte Import auf Tools..."
# Backup der bestehenden Daten erstellen
BACKUP_FILE="${BACKUP_DIR}/TSData_backup_tools_${TIMESTAMP}.sql"
log_info "Erstelle Backup der bestehenden Daten: $BACKUP_FILE"
mysqldump -u "$DB_USER" -p "$DB_NAME" \
--single-transaction \
--quick \
> "$BACKUP_FILE"
log_info "Backup erstellt: $(du -h "$BACKUP_FILE" | cut -f1)"
# Bestätigung anfordern
echo ""
log_warn "ACHTUNG: Alle bestehenden Daten in $DB_NAME werden gelöscht!"
read -p "Fortfahren? (ja/nein): " CONFIRM
if [ "$CONFIRM" != "ja" ]; then
log_info "Abgebrochen."
exit 0
fi
# Tabellen leeren
log_info "Leere bestehende Tabellen..."
mysql -u "$DB_USER" -p "$DB_NAME" << 'EOF'
SET FOREIGN_KEY_CHECKS=0;
TRUNCATE TABLE Synonyms;
TRUNCATE TABLE Linking;
TRUNCATE TABLE EntryNew;
TRUNCATE TABLE Entry;
TRUNCATE TABLE Anchor;
TRUNCATE TABLE IDSynonym;
TRUNCATE TABLE Treeview;
SET FOREIGN_KEY_CHECKS=1;
EOF
# Daten importieren
log_info "Importiere Daten aus $IMPORT_FILE..."
mysql -u "$DB_USER" -p "$DB_NAME" < "$IMPORT_FILE"
# DateModified '0000-00-00' Werte korrigieren
log_info "Korrigiere ungültige Datumswerte..."
mysql -u "$DB_USER" -p "$DB_NAME" << 'EOF'
UPDATE Entry
SET DateModified = DateCreated
WHERE DateModified = '0000-00-00 00:00:00'
OR DateModified IS NULL;
EOF
# Verifizierung
echo ""
log_info "Import abgeschlossen. Verifizierung:"
mysql -u "$DB_USER" -p -N -e "
SELECT 'Anchor', COUNT(*) FROM $DB_NAME.Anchor
UNION ALL SELECT 'Entry', COUNT(*) FROM $DB_NAME.Entry
UNION ALL SELECT 'EntryNew', COUNT(*) FROM $DB_NAME.EntryNew
UNION ALL SELECT 'IDSynonym', COUNT(*) FROM $DB_NAME.IDSynonym
UNION ALL SELECT 'Linking', COUNT(*) FROM $DB_NAME.Linking
UNION ALL SELECT 'Synonyms', COUNT(*) FROM $DB_NAME.Synonyms
UNION ALL SELECT 'Treeview', COUNT(*) FROM $DB_NAME.Treeview;
" 2>/dev/null | column -t
echo ""
log_info "Migration erfolgreich abgeschlossen!"
log_info "Backup liegt unter: $BACKUP_FILE"
}
#######################################
# VERIFY (Datenintegrität prüfen)
#######################################
do_verify() {
log_info "Prüfe Datenintegrität..."
mysql -u "$DB_USER" -p "$DB_NAME" << 'EOF'
-- Prüfe ob alle Linking-Einträge gültige Anchor-IDs haben
SELECT 'Linking -> Anchor (fehlend)' AS Check_Type, COUNT(*) AS Anzahl
FROM Linking l
LEFT JOIN Anchor a ON l.IDAnchor = a.ID
WHERE a.ID IS NULL
UNION ALL
-- Prüfe ob alle Linking-Einträge gültige Entry-IDs haben
SELECT 'Linking -> Entry (fehlend)', COUNT(*)
FROM Linking l
LEFT JOIN Entry e ON l.IDEntry = e.ID
WHERE e.ID IS NULL
UNION ALL
-- Prüfe auf ungültige Datumswerte in Entry
SELECT 'Entry mit ungültigem DateModified', COUNT(*)
FROM Entry
WHERE DateModified = '0000-00-00 00:00:00';
EOF
log_info "Integritätsprüfung abgeschlossen."
}
#######################################
# ROLLBACK (Backup wiederherstellen)
#######################################
do_rollback() {
BACKUP_FILE="$1"
if [ -z "$BACKUP_FILE" ]; then
log_info "Verfügbare Backups:"
ls -la ${BACKUP_DIR}/TSData_backup_tools_*.sql 2>/dev/null || echo "Keine Backups gefunden."
echo ""
echo "Verwendung: $0 rollback /pfad/zum/backup.sql"
exit 1
fi
if [ ! -f "$BACKUP_FILE" ]; then
log_error "Backup-Datei nicht gefunden: $BACKUP_FILE"
exit 1
fi
log_warn "Stelle Backup wieder her: $BACKUP_FILE"
read -p "Fortfahren? (ja/nein): " CONFIRM
if [ "$CONFIRM" != "ja" ]; then
log_info "Abgebrochen."
exit 0
fi
mysql -u "$DB_USER" -p "$DB_NAME" < "$BACKUP_FILE"
log_info "Rollback abgeschlossen."
}
#######################################
# MAIN
#######################################
case "$1" in
export)
do_export
;;
import)
do_import "$2"
;;
verify)
do_verify
;;
rollback)
do_rollback "$2"
;;
*)
echo "TSData Migration Script"
echo ""
echo "Verwendung: $0 {export|import|verify|rollback} [optionen]"
echo ""
echo "Befehle:"
echo " export Daten aus DSpace exportieren"
echo " import <datei> Daten in Tools importieren"
echo " verify Datenintegrität prüfen"
echo " rollback <backup> Backup wiederherstellen"
echo ""
echo "Workflow:"
echo " 1. Auf DSpace: ./migrate_tsdata.sh export"
echo " 2. Kopieren: scp /tmp/tsdata_migration/TSData_export_*.sql user@tools:/tmp/"
echo " 3. Auf Tools: ./migrate_tsdata.sh import /tmp/TSData_export_*.sql"
echo " 4. Prüfen: ./migrate_tsdata.sh verify"
exit 1
;;
esac

263
setup-libs.sh Executable file
View File

@ -0,0 +1,263 @@
#!/bin/bash
#
# setup-libs.sh - Richtet lokale JavaScript/CSS Libraries ein
#
# Verwendung:
# chmod +x setup-libs.sh
# sudo ./setup-libs.sh
#
# Struktur:
# /var/www/html/libs/
# ├── bootstrap/
# │ ├── 5.3.0/
# │ └── current -> 5.3.0
# ├── jquery/
# │ ├── 3.6.0/
# │ └── current -> 3.6.0
# ...
#
# Nutzung in HTML:
# <link href="/libs/bootstrap/current/css/bootstrap.min.css">
# <script src="/libs/jquery/current/jquery.min.js">
#
set -e
# Konfiguration
LIBS_BASE="/var/www/html/tools/libs"
WGET_OPTS="-q --show-progress"
# Farben für Ausgabe
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} Lokale Libraries Setup${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
# Prüfe Root-Rechte
if [ "$EUID" -ne 0 ]; then
echo -e "${YELLOW}Hinweis: Ohne Root-Rechte wird nach $HOME/libs installiert${NC}"
LIBS_BASE="$HOME/libs"
fi
echo -e "${GREEN}Zielverzeichnis: ${LIBS_BASE}${NC}"
echo ""
# Basisverzeichnis erstellen
mkdir -p "$LIBS_BASE"
# ============================================
# 1. Bootstrap 5.3.0
# ============================================
echo -e "${BLUE}[1/7] Bootstrap 5.3.0${NC}"
BOOTSTRAP_VERSION="5.3.0"
BOOTSTRAP_DIR="$LIBS_BASE/bootstrap/$BOOTSTRAP_VERSION"
mkdir -p "$BOOTSTRAP_DIR/css" "$BOOTSTRAP_DIR/js"
# CSS
if [ ! -f "$BOOTSTRAP_DIR/css/bootstrap.min.css" ]; then
wget $WGET_OPTS -O "$BOOTSTRAP_DIR/css/bootstrap.min.css" \
"https://cdn.jsdelivr.net/npm/bootstrap@${BOOTSTRAP_VERSION}/dist/css/bootstrap.min.css"
wget $WGET_OPTS -O "$BOOTSTRAP_DIR/css/bootstrap.min.css.map" \
"https://cdn.jsdelivr.net/npm/bootstrap@${BOOTSTRAP_VERSION}/dist/css/bootstrap.min.css.map" 2>/dev/null || true
fi
# JS
if [ ! -f "$BOOTSTRAP_DIR/js/bootstrap.bundle.min.js" ]; then
wget $WGET_OPTS -O "$BOOTSTRAP_DIR/js/bootstrap.bundle.min.js" \
"https://cdn.jsdelivr.net/npm/bootstrap@${BOOTSTRAP_VERSION}/dist/js/bootstrap.bundle.min.js"
wget $WGET_OPTS -O "$BOOTSTRAP_DIR/js/bootstrap.bundle.min.js.map" \
"https://cdn.jsdelivr.net/npm/bootstrap@${BOOTSTRAP_VERSION}/dist/js/bootstrap.bundle.min.js.map" 2>/dev/null || true
fi
# Symlink
ln -sfn "$BOOTSTRAP_VERSION" "$LIBS_BASE/bootstrap/current"
echo -e "${GREEN} ✓ Bootstrap $BOOTSTRAP_VERSION installiert${NC}"
# ============================================
# 2. jQuery 3.6.0
# ============================================
echo -e "${BLUE}[2/7] jQuery 3.6.0${NC}"
JQUERY_VERSION="3.6.0"
JQUERY_DIR="$LIBS_BASE/jquery/$JQUERY_VERSION"
mkdir -p "$JQUERY_DIR"
if [ ! -f "$JQUERY_DIR/jquery.min.js" ]; then
wget $WGET_OPTS -O "$JQUERY_DIR/jquery.min.js" \
"https://code.jquery.com/jquery-${JQUERY_VERSION}.min.js"
wget $WGET_OPTS -O "$JQUERY_DIR/jquery.min.map" \
"https://code.jquery.com/jquery-${JQUERY_VERSION}.min.map" 2>/dev/null || true
fi
ln -sfn "$JQUERY_VERSION" "$LIBS_BASE/jquery/current"
echo -e "${GREEN} ✓ jQuery $JQUERY_VERSION installiert${NC}"
# ============================================
# 3. Bootstrap Table 1.21.0
# ============================================
echo -e "${BLUE}[3/7] Bootstrap Table 1.21.0${NC}"
BSTABLE_VERSION="1.21.0"
BSTABLE_DIR="$LIBS_BASE/bootstrap-table/$BSTABLE_VERSION"
mkdir -p "$BSTABLE_DIR/css" "$BSTABLE_DIR/js" "$BSTABLE_DIR/locale"
if [ ! -f "$BSTABLE_DIR/css/bootstrap-table.min.css" ]; then
wget $WGET_OPTS -O "$BSTABLE_DIR/css/bootstrap-table.min.css" \
"https://unpkg.com/bootstrap-table@${BSTABLE_VERSION}/dist/bootstrap-table.min.css"
fi
if [ ! -f "$BSTABLE_DIR/js/bootstrap-table.min.js" ]; then
wget $WGET_OPTS -O "$BSTABLE_DIR/js/bootstrap-table.min.js" \
"https://unpkg.com/bootstrap-table@${BSTABLE_VERSION}/dist/bootstrap-table.min.js"
fi
if [ ! -f "$BSTABLE_DIR/locale/bootstrap-table-de-DE.min.js" ]; then
wget $WGET_OPTS -O "$BSTABLE_DIR/locale/bootstrap-table-de-DE.min.js" \
"https://unpkg.com/bootstrap-table@${BSTABLE_VERSION}/dist/locale/bootstrap-table-de-DE.min.js"
fi
ln -sfn "$BSTABLE_VERSION" "$LIBS_BASE/bootstrap-table/current"
echo -e "${GREEN} ✓ Bootstrap Table $BSTABLE_VERSION installiert${NC}"
# ============================================
# 4. Font Awesome 6.4.0
# ============================================
echo -e "${BLUE}[4/7] Font Awesome 6.4.0${NC}"
FA_VERSION="6.4.0"
FA_DIR="$LIBS_BASE/fontawesome/$FA_VERSION"
mkdir -p "$FA_DIR"
if [ ! -f "$FA_DIR/css/all.min.css" ]; then
# Font Awesome als ZIP herunterladen und entpacken
TEMP_DIR=$(mktemp -d)
wget $WGET_OPTS -O "$TEMP_DIR/fontawesome.zip" \
"https://use.fontawesome.com/releases/v${FA_VERSION}/fontawesome-free-${FA_VERSION}-web.zip"
unzip -q "$TEMP_DIR/fontawesome.zip" -d "$TEMP_DIR"
cp -r "$TEMP_DIR/fontawesome-free-${FA_VERSION}-web/css" "$FA_DIR/"
cp -r "$TEMP_DIR/fontawesome-free-${FA_VERSION}-web/webfonts" "$FA_DIR/"
rm -rf "$TEMP_DIR"
fi
ln -sfn "$FA_VERSION" "$LIBS_BASE/fontawesome/current"
echo -e "${GREEN} ✓ Font Awesome $FA_VERSION installiert${NC}"
# ============================================
# 5. vis.js Network 9.1.2
# ============================================
echo -e "${BLUE}[5/7] vis.js Network 9.1.2${NC}"
VIS_VERSION="9.1.2"
VIS_DIR="$LIBS_BASE/vis-network/$VIS_VERSION"
mkdir -p "$VIS_DIR"
if [ ! -f "$VIS_DIR/vis-network.min.js" ]; then
wget $WGET_OPTS -O "$VIS_DIR/vis-network.min.js" \
"https://unpkg.com/vis-network@${VIS_VERSION}/standalone/umd/vis-network.min.js"
fi
ln -sfn "$VIS_VERSION" "$LIBS_BASE/vis-network/current"
echo -e "${GREEN} ✓ vis.js Network $VIS_VERSION installiert${NC}"
# ============================================
# 6. jsTree 3.3.16
# ============================================
echo -e "${BLUE}[6/7] jsTree 3.3.16${NC}"
JSTREE_VERSION="3.3.16"
JSTREE_DIR="$LIBS_BASE/jstree/$JSTREE_VERSION"
mkdir -p "$JSTREE_DIR/themes/default"
if [ ! -f "$JSTREE_DIR/jstree.min.js" ]; then
wget $WGET_OPTS -O "$JSTREE_DIR/jstree.min.js" \
"https://cdnjs.cloudflare.com/ajax/libs/jstree/${JSTREE_VERSION}/jstree.min.js"
fi
if [ ! -f "$JSTREE_DIR/themes/default/style.min.css" ]; then
wget $WGET_OPTS -O "$JSTREE_DIR/themes/default/style.min.css" \
"https://cdnjs.cloudflare.com/ajax/libs/jstree/${JSTREE_VERSION}/themes/default/style.min.css"
# Theme-Bilder
wget $WGET_OPTS -O "$JSTREE_DIR/themes/default/32px.png" \
"https://cdnjs.cloudflare.com/ajax/libs/jstree/${JSTREE_VERSION}/themes/default/32px.png" 2>/dev/null || true
wget $WGET_OPTS -O "$JSTREE_DIR/themes/default/40px.png" \
"https://cdnjs.cloudflare.com/ajax/libs/jstree/${JSTREE_VERSION}/themes/default/40px.png" 2>/dev/null || true
wget $WGET_OPTS -O "$JSTREE_DIR/themes/default/throbber.gif" \
"https://cdnjs.cloudflare.com/ajax/libs/jstree/${JSTREE_VERSION}/themes/default/throbber.gif" 2>/dev/null || true
fi
ln -sfn "$JSTREE_VERSION" "$LIBS_BASE/jstree/current"
echo -e "${GREEN} ✓ jsTree $JSTREE_VERSION installiert${NC}"
# ============================================
# 7. Bootstrap Icons 1.11.0 (optional)
# ============================================
echo -e "${BLUE}[7/8] Bootstrap Icons 1.11.0${NC}"
BSI_VERSION="1.11.0"
BSI_DIR="$LIBS_BASE/bootstrap-icons/$BSI_VERSION"
mkdir -p "$BSI_DIR"
if [ ! -f "$BSI_DIR/bootstrap-icons.min.css" ]; then
wget $WGET_OPTS -O "$BSI_DIR/bootstrap-icons.min.css" \
"https://cdn.jsdelivr.net/npm/bootstrap-icons@${BSI_VERSION}/font/bootstrap-icons.min.css"
# Fonts-Verzeichnis
mkdir -p "$BSI_DIR/fonts"
wget $WGET_OPTS -O "$BSI_DIR/fonts/bootstrap-icons.woff" \
"https://cdn.jsdelivr.net/npm/bootstrap-icons@${BSI_VERSION}/font/fonts/bootstrap-icons.woff" 2>/dev/null || true
wget $WGET_OPTS -O "$BSI_DIR/fonts/bootstrap-icons.woff2" \
"https://cdn.jsdelivr.net/npm/bootstrap-icons@${BSI_VERSION}/font/fonts/bootstrap-icons.woff2" 2>/dev/null || true
fi
ln -sfn "$BSI_VERSION" "$LIBS_BASE/bootstrap-icons/current"
echo -e "${GREEN} ✓ Bootstrap Icons $BSI_VERSION installiert${NC}"
# ============================================
# 8. Bootstrap 3 Typeahead 4.0.2
# ============================================
echo -e "${BLUE}[8/8] Bootstrap 3 Typeahead 4.0.2${NC}"
TYPEAHEAD_VERSION="4.0.2"
TYPEAHEAD_DIR="$LIBS_BASE/bootstrap3-typeahead/$TYPEAHEAD_VERSION"
mkdir -p "$TYPEAHEAD_DIR"
if [ ! -f "$TYPEAHEAD_DIR/bootstrap3-typeahead.min.js" ]; then
wget $WGET_OPTS -O "$TYPEAHEAD_DIR/bootstrap3-typeahead.min.js" \
"https://cdnjs.cloudflare.com/ajax/libs/bootstrap-3-typeahead/${TYPEAHEAD_VERSION}/bootstrap3-typeahead.min.js"
fi
ln -sfn "$TYPEAHEAD_VERSION" "$LIBS_BASE/bootstrap3-typeahead/current"
echo -e "${GREEN} ✓ Bootstrap 3 Typeahead $TYPEAHEAD_VERSION installiert${NC}"
# ============================================
# Berechtigungen setzen
# ============================================
echo ""
echo -e "${BLUE}Setze Berechtigungen...${NC}"
if [ "$EUID" -eq 0 ]; then
chown -R www-data:www-data "$LIBS_BASE"
fi
chmod -R 755 "$LIBS_BASE"
# ============================================
# Übersicht anzeigen
# ============================================
echo ""
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN} Installation abgeschlossen!${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""
echo "Struktur:"
echo ""
find "$LIBS_BASE" -maxdepth 3 -type d | head -30
echo ""
echo "Symlinks:"
ls -la "$LIBS_BASE"/*/current 2>/dev/null || true
echo ""
echo -e "${YELLOW}Hinweis: Passen Sie die Header.html entsprechend an.${NC}"
echo ""
echo "Beispiel-Pfade:"
echo " /libs/bootstrap/current/css/bootstrap.min.css"
echo " /libs/jquery/current/jquery.min.js"
echo " /libs/fontawesome/current/css/all.min.css"
echo ""

840
styles.css Normal file
View File

@ -0,0 +1,840 @@
/* styles.css - BIBB Thesaurus (ORCID zu DSpace Layout) */
/* ===========================
Variablen & Reset
=========================== */
:root {
--primary-color: #1a3a5c;
--primary-light: #2a5a8c;
--primary-dark: #0f2840;
--secondary-color: #006699;
--accent-color: #00a3cc;
--success-color: #28a745;
--warning-color: #ffc107;
--danger-color: #dc3545;
--info-color: #17a2b8;
--text-primary: #333;
--text-secondary: #666;
--text-muted: #999;
--bg-gradient: linear-gradient(135deg, #e8eef5 0%, #f0f4f8 100%);
--bg-light: #f8f9fa;
--bg-lighter: #fafbfc;
--border-color: #e0e0e0;
--border-light: #f0f0f0;
--shadow-sm: 0 2px 4px rgba(0,0,0,0.08);
--shadow-md: 0 4px 12px rgba(0,0,0,0.1);
--shadow-lg: 0 8px 24px rgba(0,0,0,0.12);
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--transition: all 0.3s ease;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
line-height: 1.6;
color: var(--text-primary);
background: var(--bg-gradient);
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* ===========================
Layout Container
=========================== */
.page-wrapper {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
.main-content {
flex: 1;
padding: 40px 0;
}
/* ===========================
Header & Navigation
=========================== */
.site-header {
background: var(--primary-color);
color: white;
padding: 0;
box-shadow: var(--shadow-md);
}
.site-header .container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
}
.site-logo {
display: flex;
flex-direction: column;
text-decoration: none;
color: white;
}
.site-title {
font-size: 1.4em;
font-weight: 700;
color: white;
}
.site-subtitle {
font-size: 0.85em;
opacity: 0.8;
font-weight: 400;
}
.main-nav ul {
list-style: none;
display: flex;
gap: 5px;
margin: 0;
padding: 0;
}
.main-nav a {
color: white;
text-decoration: none;
font-weight: 500;
transition: var(--transition);
padding: 10px 18px;
border-radius: var(--radius-sm);
display: block;
font-size: 0.95em;
}
.main-nav a:hover,
.main-nav a.active {
background: rgba(255,255,255,0.15);
}
/* ===========================
Cards
=========================== */
.card {
background: white;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
padding: 35px 40px;
margin-bottom: 30px;
}
.card-title {
font-size: 1.6em;
font-weight: 600;
color: var(--primary-color);
margin: 0 0 15px 0;
}
.card-description {
color: var(--text-secondary);
font-size: 1em;
margin-bottom: 30px;
}
/* ===========================
Nav Cards (Feature Cards)
=========================== */
.nav-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 25px;
margin-top: 10px;
}
.nav-card {
background: white;
border-radius: var(--radius-md);
padding: 25px 30px;
border: 1px solid var(--border-color);
border-left: 4px solid var(--border-color);
transition: var(--transition);
text-decoration: none;
color: inherit;
display: block;
}
.nav-card:hover {
border-left-color: var(--primary-color);
box-shadow: var(--shadow-md);
transform: translateY(-2px);
}
.nav-card-icon {
font-size: 2.5em;
margin-bottom: 15px;
display: block;
}
.nav-card h3 {
font-size: 1.15em;
font-weight: 600;
color: var(--primary-color);
margin-bottom: 10px;
}
.nav-card p {
color: var(--text-secondary);
font-size: 0.9em;
margin: 0;
line-height: 1.5;
}
/* ===========================
Section Headers
=========================== */
.section-title {
font-size: 1.3em;
font-weight: 600;
color: var(--text-primary);
margin: 30px 0 20px 0;
}
/* ===========================
Project Items / List Items
=========================== */
.project-list {
display: flex;
flex-direction: column;
gap: 15px;
}
.project-item {
background: white;
border-radius: var(--radius-md);
padding: 20px 25px;
border: 1px solid var(--border-color);
border-left: 4px solid var(--primary-color);
display: flex;
justify-content: space-between;
align-items: center;
transition: var(--transition);
}
.project-item:hover {
box-shadow: var(--shadow-sm);
}
.project-info h4 {
font-size: 1.1em;
font-weight: 600;
color: var(--primary-color);
margin: 0 0 5px 0;
}
.project-meta {
font-size: 0.85em;
color: var(--text-muted);
}
.project-actions {
display: flex;
gap: 10px;
}
/* ===========================
Buttons
=========================== */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 10px 20px;
border-radius: var(--radius-sm);
font-weight: 500;
text-decoration: none;
border: none;
cursor: pointer;
transition: var(--transition);
font-size: 0.9em;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-sm);
}
.btn-primary {
background: var(--primary-color);
color: white;
}
.btn-primary:hover {
background: var(--primary-light);
color: white;
}
.btn-secondary {
background: var(--secondary-color);
color: white;
}
.btn-secondary:hover {
background: var(--primary-color);
}
.btn-success {
background: var(--success-color);
color: white;
}
.btn-success:hover {
background: #218838;
}
.btn-danger {
background: var(--danger-color);
color: white;
}
.btn-danger:hover {
background: #c82333;
}
.btn-info {
background: var(--info-color);
color: white;
}
.btn-info:hover {
background: #138496;
}
.btn-outline {
background: transparent;
border: 2px solid var(--primary-color);
color: var(--primary-color);
}
.btn-outline:hover {
background: var(--primary-color);
color: white;
}
.btn-sm {
padding: 6px 14px;
font-size: 0.85em;
}
.btn-lg {
padding: 14px 28px;
font-size: 1em;
}
/* ===========================
Tables - Bootstrap Table Anpassungen
=========================== */
.fixed-table-container {
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
overflow: hidden;
}
.fixed-table-toolbar {
background: var(--bg-light);
padding: 15px 20px;
border-bottom: 1px solid var(--border-color);
}
.fixed-table-toolbar .search input {
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
padding: 10px 15px;
font-size: 0.95em;
}
.fixed-table-toolbar .search input:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(26,58,92,0.1);
outline: none;
}
.bootstrap-table .table {
margin-bottom: 0;
}
.bootstrap-table .table thead th {
background: var(--primary-color);
color: white;
font-weight: 600;
border: none;
padding: 14px 12px;
font-size: 0.95em;
}
.bootstrap-table .table tbody tr {
transition: var(--transition);
}
.bootstrap-table .table tbody tr:hover {
background: rgba(26,58,92,0.03);
}
.bootstrap-table .table tbody td {
padding: 14px 12px;
vertical-align: middle;
border-color: var(--border-light);
}
/* Suchfeld doppelt so breit */
.bootstrap-table .search input.form-control {
width: 400px; /* Standard ist ~200px, Wert nach Bedarf anpassen */
}
.fixed-table-pagination {
padding: 15px 20px;
background: var(--bg-light);
border-top: 1px solid var(--border-color);
}
.page-item.active .page-link {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.page-link {
color: var(--primary-color);
padding: 8px 14px;
}
.page-link:hover {
color: var(--primary-dark);
background: var(--bg-light);
}
/* Action Buttons in Tabelle */
.table-actions {
display: flex;
gap: 6px;
justify-content: center;
}
.table-actions .btn {
width: 34px;
height: 34px;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-sm);
}
.table-actions .btn:hover {
transform: scale(1.1);
}
/* ===========================
Forms
=========================== */
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
font-weight: 500;
margin-bottom: 8px;
color: var(--text-primary);
}
.form-control {
width: 100%;
padding: 12px 15px;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
font-size: 1em;
transition: var(--transition);
}
.form-control:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(26,58,92,0.1);
outline: none;
}
.form-control:disabled {
background: var(--bg-light);
cursor: not-allowed;
color: var(--text-muted);
}
.form-select {
width: 100%;
padding: 12px 15px;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
font-size: 1em;
background: white;
cursor: pointer;
}
.form-select:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(26,58,92,0.1);
outline: none;
}
textarea.form-control {
resize: vertical;
min-height: 100px;
}
/* ===========================
Modals
=========================== */
.modal-content {
border: none;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
}
.modal-header {
background: var(--primary-color);
color: white;
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
padding: 18px 25px;
border-bottom: none;
}
.modal-header .modal-title {
font-weight: 600;
font-size: 1.15em;
display: flex;
align-items: center;
gap: 10px;
}
.modal-header .btn-close {
filter: brightness(0) invert(1);
opacity: 0.8;
}
.modal-header .btn-close:hover {
opacity: 1;
}
.modal-body {
padding: 30px;
}
.modal-footer {
padding: 18px 25px;
border-top: 1px solid var(--border-light);
background: var(--bg-lighter);
border-radius: 0 0 var(--radius-lg) var(--radius-lg);
gap: 10px;
}
/* ===========================
Alerts
=========================== */
.alert {
padding: 15px 20px;
border-radius: var(--radius-md);
margin-bottom: 20px;
border: none;
display: flex;
align-items: center;
gap: 12px;
}
.alert i {
font-size: 1.2em;
}
.alert-success {
background: #d4edda;
color: #155724;
}
.alert-danger {
background: #f8d7da;
color: #721c24;
}
.alert-warning {
background: #fff3cd;
color: #856404;
}
.alert-info {
background: #d1ecf1;
color: #0c5460;
}
/* ===========================
Autocomplete Dropdown
=========================== */
.autocomplete-dropdown {
position: fixed;
z-index: 9999;
background: white;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
box-shadow: var(--shadow-lg);
max-height: 300px;
overflow-y: auto;
}
.autocomplete-dropdown .dropdown-item {
padding: 12px 16px;
cursor: pointer;
color: var(--text-primary);
text-decoration: none;
display: block;
transition: var(--transition);
border-bottom: 1px solid var(--border-light);
}
.autocomplete-dropdown .dropdown-item:last-child {
border-bottom: none;
}
.autocomplete-dropdown .dropdown-item:hover,
.autocomplete-dropdown .dropdown-item.active {
background: var(--primary-color);
color: white;
}
/* ===========================
Relations Table
=========================== */
.relations-table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
}
.relations-table th {
background: var(--bg-light);
padding: 12px 15px;
text-align: left;
font-weight: 600;
border-bottom: 2px solid var(--border-color);
color: var(--text-secondary);
}
.relations-table td {
padding: 12px 15px;
border-bottom: 1px solid var(--border-light);
}
.relations-table tr:hover {
background: rgba(26,58,92,0.02);
}
/* ===========================
Stats Grid
=========================== */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 20px;
margin-top: 20px;
}
.stat-item {
background: var(--bg-light);
padding: 20px;
border-radius: var(--radius-md);
text-align: center;
border: 1px solid var(--border-light);
}
.stat-number {
font-size: 2.2em;
font-weight: 700;
color: var(--primary-color);
line-height: 1;
}
.stat-label {
font-size: 0.9em;
color: var(--text-secondary);
margin-top: 8px;
}
/* ===========================
Footer
=========================== */
.site-footer {
background: var(--primary-dark);
color: white;
padding: 20px 0;
margin-top: auto;
}
.site-footer .container {
display: flex;
justify-content: space-between;
align-items: center;
}
.footer-links {
list-style: none;
display: flex;
gap: 25px;
margin: 0;
padding: 0;
}
.footer-links a {
color: rgba(255,255,255,0.7);
text-decoration: none;
transition: var(--transition);
font-size: 0.9em;
}
.footer-links a:hover {
color: white;
}
.footer-copyright {
color: rgba(255,255,255,0.6);
font-size: 0.85em;
}
/* ===========================
Utilities
=========================== */
.text-center { text-align: center; }
.text-right { text-align: right; }
.text-muted { color: var(--text-muted); }
.mt-10 { margin-top: 10px; }
.mt-20 { margin-top: 20px; }
.mt-30 { margin-top: 30px; }
.mb-10 { margin-bottom: 10px; }
.mb-20 { margin-bottom: 20px; }
.mb-30 { margin-bottom: 30px; }
.d-none { display: none; }
.d-block { display: block; }
.d-flex { display: flex; }
.justify-between { justify-content: space-between; }
.align-center { align-items: center; }
.gap-10 { gap: 10px; }
.gap-20 { gap: 20px; }
.w-100 { width: 100%; }
/* ===========================
Responsive
=========================== */
@media (max-width: 992px) {
.site-header .container {
flex-direction: column;
gap: 15px;
}
.main-nav ul {
flex-wrap: wrap;
justify-content: center;
}
.card {
padding: 25px;
}
.nav-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.site-footer .container {
flex-direction: column;
gap: 15px;
text-align: center;
}
.footer-links {
flex-wrap: wrap;
justify-content: center;
}
.project-item {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
.project-actions {
width: 100%;
}
.project-actions .btn {
flex: 1;
}
.stats-grid {
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 480px) {
.card-title {
font-size: 1.3em;
}
.stats-grid {
grid-template-columns: 1fr;
}
.btn {
width: 100%;
justify-content: center;
}
.table-actions {
flex-direction: column;
}
}
/* ===========================
Print Styles
=========================== */
@media print {
.site-header,
.site-footer,
.btn,
.table-actions {
display: none;
}
.card {
box-shadow: none;
border: 1px solid var(--border-color);
}
body {
background: white;
}
}

View File

@ -0,0 +1,55 @@
<!-- Modal - Klassifikation Details (Read-Only) -->
<div class="modal fade" id="DetailsModal" tabindex="-1" aria-labelledby="DetailsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" style="max-width: 1024px;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="DetailsModalHeadline">
<i class="fas fa-folder-tree"></i> Klassifikation - Details
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
</div>
<div class="modal-body">
<!-- Formular (Read-Only) -->
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="display_notation">Notation</label>
<div class="col-sm-3">
<input type="text" id="display_notation" class="form-control" disabled />
</div>
</div>
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="display_term">Bezeichnung</label>
<div class="col-sm-9">
<input type="text" id="display_term" class="form-control" disabled />
<input type="hidden" id="display_id" />
</div>
</div>
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="display_descriptor">Deskriptor</label>
<div class="col-sm-9">
<input type="text" id="display_descriptor" class="form-control" disabled />
</div>
</div>
<div class="form-group mb-3">
<label class="form-label" for="display_scopenote">Anmerkung</label>
<textarea class="form-control" id="display_scopenote" rows="3" disabled></textarea>
</div>
<!-- Relationen-Bereich -->
<hr>
<h5 class="mb-3"><i class="fas fa-link"></i> Relationen</h5>
<div class="modal_content"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times"></i> schliessen
</button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1 @@
<!-- ClassificationsModal - Platzhalter für zukünftige Erweiterungen -->

View File

@ -0,0 +1,317 @@
<!-- Modal - Körperschaft Details mit semantischem Netz -->
<div class="modal fade" id="DetailsModal" tabindex="-1" aria-labelledby="DetailsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl" style="max-width: 1200px;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="DetailsModalHeadline">
<i class="fas fa-building"></i> Detailanzeige Körperschaft
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
</div>
<div class="modal-body">
<div class="row">
<!-- Linke Spalte: Stammdaten -->
<div class="col-md-4">
<div class="detail-info-panel">
<div class="form-group mb-3">
<label class="form-label fw-bold">Name</label>
<input type="text" id="display_term" class="form-control" disabled />
<input type="hidden" id="display_id" />
</div>
<div class="form-group mb-3">
<label class="form-label fw-bold">Deskriptor</label>
<input type="text" id="display_descriptor" class="form-control" disabled />
</div>
<div class="row mb-3">
<div class="col-6">
<label class="form-label fw-bold">Type</label>
<input type="text" id="display_type" class="form-control" disabled />
</div>
<div class="col-6">
<label class="form-label fw-bold">ID</label>
<input type="text" id="display_id_show" class="form-control" disabled />
</div>
</div>
<div class="form-group mb-3">
<label class="form-label fw-bold">Scopenote</label>
<textarea class="form-control" id="display_scopenote" rows="4" disabled></textarea>
</div>
</div>
</div>
<!-- Rechte Spalte: Semantisches Netz -->
<div class="col-md-8">
<div class="network-panel">
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="mb-0"><i class="fas fa-project-diagram me-2"></i>Semantisches Netz</h6>
<div class="network-toolbar">
<button class="btn btn-sm btn-outline-secondary" onclick="networkFit()" title="Ansicht anpassen">
<i class="fas fa-expand"></i>
</button>
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomIn()" title="Vergrößern">
<i class="fas fa-search-plus"></i>
</button>
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomOut()" title="Verkleinern">
<i class="fas fa-search-minus"></i>
</button>
</div>
</div>
<div id="network-container"></div>
<div class="network-legend">
<span class="legend-item"><span class="legend-dot" style="background: #1a3a5c;"></span> Zentrale Körperschaft</span>
<span class="legend-item"><span class="legend-dot" style="background: #28a745;"></span> Früher verwendet</span>
<span class="legend-item"><span class="legend-dot" style="background: #dc3545;"></span> Später verwendet</span>
<span class="legend-item"><span class="legend-dot" style="background: #fd7e14;"></span> Alternativer Name</span>
<span class="legend-item"><span class="legend-dot" style="background: #6c757d;"></span> USE/USEDFOR</span>
</div>
</div>
</div>
</div>
<div class="modal_content" style="display: none;"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times"></i> schliessen
</button>
</div>
</div>
</div>
</div>
<style>
#network-container {
width: 100%;
height: 450px;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
}
.network-panel {
background: white;
border-radius: var(--radius-md);
padding: 15px;
border: 1px solid var(--border-color);
}
.detail-info-panel {
background: var(--bg-light);
border-radius: var(--radius-md);
padding: 15px;
border: 1px solid var(--border-color);
}
.network-toolbar {
display: flex;
gap: 5px;
}
.network-toolbar .btn {
padding: 4px 8px;
}
.network-legend {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid var(--border-light);
font-size: 12px;
}
.legend-item {
display: flex;
align-items: center;
gap: 5px;
}
.legend-dot {
width: 12px;
height: 12px;
border-radius: 50%;
display: inline-block;
}
#DetailsModal .modal-body {
max-height: 80vh;
overflow-y: auto;
}
</style>
<script src="/libs/vis-network/current/vis-network.min.js"></script>
<script>if (typeof vis === "undefined") { document.write('<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"><\/script>'); }</script>
<script>
var network = null;
var networkData = null;
function createSemanticNetwork(centralTerm, centralId, relations) {
console.log('🕸️ Erstelle semantisches Netz für Körperschaft:', centralTerm);
var nodes = [];
var edges = [];
nodes.push({
id: centralId,
label: wrapLabel(centralTerm, 20),
shape: 'ellipse',
color: {
background: '#1a3a5c',
border: '#0d1f30',
highlight: { background: '#2a5a8c', border: '#1a3a5c' },
hover: { background: '#2a5a8c', border: '#1a3a5c' }
},
font: { color: '#ffffff', size: 14, face: 'Arial', bold: true },
size: 35,
borderWidth: 3,
shadow: true
});
// Farben für Körperschafts-Relationstypen
var relationColors = {
'USEDEARLIER': { node: '#28a745', edge: '#28a745', label: 'Früher verwendet' },
'USEDLATER': { node: '#dc3545', edge: '#dc3545', label: 'Später verwendet' },
'ALTERNATIVENAME': { node: '#fd7e14', edge: '#fd7e14', label: 'Alternativer Name' },
'USE': { node: '#6c757d', edge: '#6c757d', label: 'Benutze' },
'USEDFOR': { node: '#6c757d', edge: '#6c757d', label: 'Benutzt für' }
};
if (relations && relations.length > 0) {
relations.forEach(function(rel, index) {
var relType = rel.Relationtype || 'USE';
var relColor = relationColors[relType] || { node: '#17a2b8', edge: '#17a2b8' };
var relId = rel.IDRelation || ('rel_' + index);
var relText = rel.TextRelation || 'Unbekannt';
nodes.push({
id: relId,
label: wrapLabel(relText, 18),
shape: 'ellipse',
color: {
background: relColor.node,
border: shadeColor(relColor.node, -20),
highlight: { background: shadeColor(relColor.node, 20), border: relColor.node },
hover: { background: shadeColor(relColor.node, 20), border: relColor.node }
},
font: { color: '#ffffff', size: 12, face: 'Arial' },
size: 25,
borderWidth: 2,
shadow: true,
title: relType + ': ' + relText + ' (ID: ' + relId + ')'
});
var arrowDirection = {};
if (relType === 'USEDEARLIER') {
arrowDirection = { from: { enabled: true, scaleFactor: 1 } };
} else if (relType === 'USEDLATER') {
arrowDirection = { to: { enabled: true, scaleFactor: 1 } };
}
edges.push({
from: centralId,
to: relId,
label: relType,
color: { color: relColor.edge, highlight: relColor.edge, hover: relColor.edge },
width: 2,
arrows: arrowDirection,
font: { size: 10, color: '#666', strokeWidth: 3, strokeColor: '#ffffff' },
smooth: { type: 'curvedCW', roundness: 0.2 }
});
});
}
var container = document.getElementById('network-container');
networkData = {
nodes: new vis.DataSet(nodes),
edges: new vis.DataSet(edges)
};
var options = {
nodes: { shape: 'ellipse', scaling: { min: 20, max: 40 } },
edges: { smooth: { type: 'curvedCW', roundness: 0.2 } },
physics: {
enabled: true,
barnesHut: {
gravitationalConstant: -3000,
centralGravity: 0.3,
springLength: 150,
springConstant: 0.04,
damping: 0.09
},
stabilization: { enabled: true, iterations: 200, updateInterval: 25 }
},
interaction: { hover: true, tooltipDelay: 200, zoomView: true, dragView: true },
layout: { improvedLayout: true }
};
network = new vis.Network(container, networkData, options);
network.on('click', function(params) {
if (params.nodes.length > 0) {
var clickedNodeId = params.nodes[0];
if (clickedNodeId != centralId) {
ShowModalDetails(clickedNodeId);
}
}
});
network.once('stabilizationIterationsDone', function() {
network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
});
}
function wrapLabel(text, maxLength) {
if (!text) return '';
if (text.length <= maxLength) return text;
var words = text.split(' ');
var lines = [];
var currentLine = '';
words.forEach(function(word) {
if ((currentLine + ' ' + word).trim().length <= maxLength) {
currentLine = (currentLine + ' ' + word).trim();
} else {
if (currentLine) lines.push(currentLine);
currentLine = word;
}
});
if (currentLine) lines.push(currentLine);
return lines.join('\n');
}
function shadeColor(color, percent) {
var R = parseInt(color.substring(1,3), 16);
var G = parseInt(color.substring(3,5), 16);
var B = parseInt(color.substring(5,7), 16);
R = parseInt(R * (100 + percent) / 100);
G = parseInt(G * (100 + percent) / 100);
B = parseInt(B * (100 + percent) / 100);
R = (R < 255) ? R : 255;
G = (G < 255) ? G : 255;
B = (B < 255) ? B : 255;
var RR = ((R.toString(16).length == 1) ? "0" + R.toString(16) : R.toString(16));
var GG = ((G.toString(16).length == 1) ? "0" + G.toString(16) : G.toString(16));
var BB = ((B.toString(16).length == 1) ? "0" + B.toString(16) : B.toString(16));
return "#" + RR + GG + BB;
}
function networkFit() {
if (network) network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
}
function networkZoomIn() {
if (network) network.moveTo({ scale: network.getScale() * 1.3, animation: { duration: 300 } });
}
function networkZoomOut() {
if (network) network.moveTo({ scale: network.getScale() / 1.3, animation: { duration: 300 } });
}
</script>

View File

@ -0,0 +1 @@
<!-- CorporatesModal - Platzhalter für zukünftige Erweiterungen -->

16
templates/Footer.html Normal file
View File

@ -0,0 +1,16 @@
<!-- Site Footer -->
<footer class="site-footer">
<div class="container">
<div class="footer-copyright">
&copy; <?php echo date('Y'); ?> Bundesinstitut für Berufsbildung (BIBB)
</div>
<ul class="footer-links">
<li><a href="https://www.bibb.de/de/impressum_8945.php" target="_blank">Impressum</a></li>
<li><a href="https://www.bibb.de/de/datenschutzerklaerung_81271.php" target="_blank">Datenschutz</a></li>
<li><a href="https://www.bibb.de/de/kontakt_3922.php" target="_blank">Kontakt</a></li>
</ul>
</div>
</footer>
</body>
</html>

709
templates/Header.html Normal file
View File

@ -0,0 +1,709 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BIBB Thesaurus-Verwaltung</title>
<!-- ============================================
LOKALE LIBRARIES MIT CDN-FALLBACK
Pfade verweisen auf /libs/[library]/current/
Falls lokal nicht verfügbar, wird CDN geladen.
Setup: ./setup-libs.sh ausführen
============================================ -->
<!-- Bootstrap 5 CSS (lokal mit Fallback) -->
<link href="/libs/bootstrap/current/css/bootstrap.min.css" rel="stylesheet"
onerror="this.onerror=null; this.href='https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css';">
<!-- Font Awesome 6 (lokal mit Fallback) -->
<link rel="stylesheet" href="/libs/fontawesome/current/css/all.min.css"
onerror="this.onerror=null; this.href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css';">
<!-- Bootstrap Table CSS (lokal mit Fallback) -->
<link rel="stylesheet" href="/libs/bootstrap-table/current/css/bootstrap-table.min.css"
onerror="this.onerror=null; this.href='https://unpkg.com/bootstrap-table@1.21.4/dist/bootstrap-table.min.css';">
<!-- jsTree CSS (lokal mit Fallback) -->
<link rel="stylesheet" href="/libs/jstree/current/themes/default/style.min.css"
onerror="this.onerror=null; this.href='https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.16/themes/default/style.min.css';">
<!-- BIBB Custom Styles -->
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="./styles.css">
<link rel="stylesheet" href="../styles.css">
<!-- Kritische Styles als Fallback direkt eingebettet -->
<style>
:root {
--primary-color: #1a3a5c;
--primary-light: #2a5a8c;
--primary-dark: #0f2840;
--secondary-color: #006699;
--text-primary: #333;
--text-secondary: #666;
--text-muted: #999;
--bg-gradient: linear-gradient(135deg, #e8eef5 0%, #f0f4f8 100%);
--bg-light: #f8f9fa;
--border-color: #e0e0e0;
--border-light: #f0f0f0;
--shadow-md: 0 4px 12px rgba(0,0,0,0.1);
--radius-md: 8px;
--radius-lg: 12px;
--transition: all 0.3s ease;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
line-height: 1.6;
color: var(--text-primary);
background: var(--bg-gradient);
min-height: 100vh;
display: flex;
flex-direction: column;
}
.page-wrapper {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
.main-content {
flex: 1;
padding: 40px 0;
}
/* Header */
.site-header {
background: var(--primary-color);
color: white;
padding: 0;
box-shadow: var(--shadow-md);
}
.site-header .container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
}
.site-logo {
display: flex;
flex-direction: column;
text-decoration: none;
color: white;
}
.site-title {
font-size: 1.4em;
font-weight: 700;
color: white;
}
.site-subtitle {
font-size: 0.85em;
opacity: 0.8;
}
.main-nav ul {
list-style: none;
display: flex;
gap: 5px;
margin: 0;
padding: 0;
}
.main-nav a {
color: white;
text-decoration: none;
font-weight: 500;
padding: 10px 18px;
border-radius: 4px;
display: block;
font-size: 0.95em;
transition: var(--transition);
}
.main-nav a:hover {
background: rgba(255,255,255,0.15);
}
/* Dropdown Menu Styles */
.main-nav .nav-item {
position: relative;
}
.main-nav .dropdown-menu {
display: none;
position: absolute;
top: 100%;
left: 0;
background: white;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
box-shadow: var(--shadow-md);
min-width: 200px;
padding: 8px 0;
z-index: 1000;
list-style: none;
margin: 0;
}
.main-nav .nav-item:hover .dropdown-menu,
.main-nav .nav-item.show .dropdown-menu {
display: block;
}
.main-nav .dropdown-menu li {
margin: 0;
}
.main-nav .dropdown-menu .dropdown-item {
color: var(--text-primary) !important;
padding: 10px 20px;
font-weight: 400;
border-radius: 0;
background: white;
}
.main-nav .dropdown-menu .dropdown-item:hover {
background: var(--bg-light) !important;
color: var(--primary-color) !important;
}
/* Cards */
.card {
background: white;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
padding: 35px 40px;
margin-bottom: 30px;
}
.card-title {
font-size: 1.6em;
font-weight: 600;
color: var(--primary-color);
margin: 0 0 15px 0;
}
.card-description {
color: var(--text-secondary);
font-size: 1em;
margin-bottom: 30px;
}
/* Nav Cards Grid */
.nav-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 25px;
margin-top: 10px;
}
.nav-card {
background: white;
border-radius: var(--radius-md);
padding: 25px 30px;
border: 1px solid var(--border-color);
border-left: 4px solid var(--border-color);
transition: var(--transition);
text-decoration: none;
color: inherit;
display: block;
}
.nav-card:hover {
border-left-color: var(--primary-color);
box-shadow: var(--shadow-md);
transform: translateY(-2px);
text-decoration: none;
color: inherit;
}
.nav-card-icon {
font-size: 2.5em;
margin-bottom: 15px;
display: block;
}
.nav-card h3 {
font-size: 1.15em;
font-weight: 600;
color: var(--primary-color);
margin-bottom: 10px;
}
.nav-card p {
color: var(--text-secondary);
font-size: 0.9em;
margin: 0;
line-height: 1.5;
}
/* Section Title */
.section-title {
font-size: 1.3em;
font-weight: 600;
color: var(--text-primary);
margin: 30px 0 20px 0;
}
/* Stats Grid */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 20px;
}
.stat-item {
background: var(--bg-light);
padding: 20px;
border-radius: var(--radius-md);
text-align: center;
border: 1px solid var(--border-light);
}
.stat-number {
font-size: 2.2em;
font-weight: 700;
color: var(--primary-color);
line-height: 1;
}
.stat-label {
font-size: 0.9em;
color: var(--text-secondary);
margin-top: 8px;
}
/* Footer */
.site-footer {
background: var(--primary-color);
color: white;
padding: 20px 0;
margin-top: auto;
}
.site-footer .container {
display: flex;
justify-content: space-between;
align-items: center;
}
.site-footer a {
color: rgba(255,255,255,0.8);
text-decoration: none;
}
.site-footer a:hover {
color: white;
}
/* Buttons */
.btn-primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.btn-primary:hover {
background-color: var(--primary-light);
border-color: var(--primary-light);
}
.btn-success {
background-color: #28a745;
border-color: #28a745;
}
.btn-success:hover {
background-color: #218838;
border-color: #1e7e34;
}
.btn-danger {
background-color: #dc3545;
border-color: #dc3545;
}
.btn-danger:hover {
background-color: #c82333;
border-color: #bd2130;
}
/* Relations Table */
#relation_table,
.new_modal_content {
margin-top: 15px;
}
#relation_table table,
.new_modal_content table {
border-collapse: separate;
border-spacing: 0;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
overflow: hidden;
}
#relation_table table thead th,
.new_modal_content table thead th {
background: var(--primary-color);
color: white;
font-weight: 600;
border: none;
padding: 12px 15px;
}
#relation_table table thead tr:first-child th:first-child,
.new_modal_content table thead tr:first-child th:first-child {
border-top-left-radius: var(--radius-md);
}
#relation_table table thead tr:first-child th:last-child,
.new_modal_content table thead tr:first-child th:last-child {
border-top-right-radius: var(--radius-md);
}
#relation_table table tbody tr:last-child td:first-child,
.new_modal_content table tbody tr:last-child td:first-child {
border-bottom-left-radius: var(--radius-md);
}
#relation_table table tbody tr:last-child td:last-child,
.new_modal_content table tbody tr:last-child td:last-child {
border-bottom-right-radius: var(--radius-md);
}
#relation_table .table,
.new_modal_content .table {
margin-bottom: 0;
}
/* Autocomplete Dropdown Styles */
.autocomplete-dropdown {
background: white;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
box-shadow: var(--shadow-md);
max-height: 300px;
overflow-y: auto;
}
.autocomplete-dropdown .dropdown-item {
display: block;
padding: 10px 15px;
color: var(--text-primary);
text-decoration: none;
border-bottom: 1px solid var(--border-light);
cursor: pointer;
transition: var(--transition);
}
.autocomplete-dropdown .dropdown-item:last-child {
border-bottom: none;
}
.autocomplete-dropdown .dropdown-item:hover {
background: var(--bg-light);
color: var(--primary-color);
}
/* Bootstrap Table Pagination in BIBB-Blau */
.pagination .page-link {
color: var(--primary-color);
border-color: var(--border-color);
}
.pagination .page-link:hover {
color: white;
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.pagination .page-item.active .page-link {
background-color: var(--primary-color);
border-color: var(--primary-color);
color: white;
}
.pagination .page-item.disabled .page-link {
color: var(--text-muted);
}
/* Kopier-Icons und Links in Tabelle */
.fixed-table-container a,
.bootstrap-table a,
.fixed-table-body a {
color: var(--primary-color);
}
.fixed-table-container a:hover,
.bootstrap-table a:hover,
.fixed-table-body a:hover {
color: var(--primary-light);
}
/* Toolbar Icons */
.fixed-table-toolbar .btn-secondary,
.fixed-table-toolbar .btn-default {
color: var(--primary-color);
border-color: var(--border-color);
}
.fixed-table-toolbar .btn-secondary:hover,
.fixed-table-toolbar .btn-default:hover {
background-color: var(--primary-color);
color: white;
border-color: var(--primary-color);
}
/* Dropdown und Page-Size Selector */
.fixed-table-pagination .page-list .btn,
.fixed-table-pagination select,
.fixed-table-pagination .page-list .dropdown-toggle,
.fixed-table-pagination .page-list button {
color: var(--primary-color);
border-color: var(--border-color);
background-color: var(--bg-light);
}
.fixed-table-pagination .page-list .dropdown-menu {
background-color: var(--bg-light);
}
.fixed-table-pagination .page-list .dropdown-item {
color: var(--primary-color);
}
.fixed-table-pagination .page-list .dropdown-item:hover,
.fixed-table-pagination .page-list .dropdown-item.active {
background-color: var(--primary-color);
color: white;
}
/* Detail-View Toggle Icons */
.detail-icon {
color: var(--primary-color) !important;
}
/* Bearbeiten/Löschen Icons in Tabelle weiß */
.table-actions .btn i,
.table-actions .btn-primary i,
.table-actions .btn-danger i {
color: white !important;
}
.btn-sm {
padding: 6px 14px;
font-size: 0.85em;
}
/* Tables */
.bootstrap-table .table thead th {
background: var(--primary-color);
color: white;
font-weight: 600;
border: none;
padding: 14px 12px;
}
.fixed-table-container {
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
overflow: hidden;
}
.fixed-table-toolbar {
background: var(--bg-light);
padding: 15px 20px;
border-bottom: 1px solid var(--border-color);
}
/* Modals */
.modal-header {
background: var(--primary-color);
color: white;
border-radius: 12px 12px 0 0;
padding: 18px 25px;
border-bottom: none;
}
.modal-header .btn-close {
filter: brightness(0) invert(1);
}
.modal-content {
border: none;
border-radius: 12px;
}
/* Forms */
.form-control:focus, .form-select:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(26,58,92,0.1);
}
/* Utilities */
.d-flex { display: flex; }
.justify-between { justify-content: space-between; }
.align-center { align-items: center; }
.mb-20 { margin-bottom: 20px; }
.mt-20 { margin-top: 20px; }
.mt-30 { margin-top: 30px; }
.w-100 { width: 100%; }
/* Table Actions */
.table-actions {
display: flex;
gap: 6px;
justify-content: center;
}
.table-actions .btn {
width: 34px;
height: 34px;
padding: 0;
}
/* Responsive */
@media (max-width: 992px) {
.site-header .container {
flex-direction: column;
gap: 15px;
}
.nav-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.site-footer .container {
flex-direction: column;
gap: 15px;
text-align: center;
}
.card {
padding: 25px;
}
}
</style>
<!-- jQuery (lokal mit CDN-Fallback) -->
<script src="/libs/jquery/current/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="https://code.jquery.com/jquery-3.6.0.min.js"><\/script>')</script>
<!-- Bootstrap 5 JS (lokal mit CDN-Fallback) -->
<script src="/libs/bootstrap/current/js/bootstrap.bundle.min.js"></script>
<script>
if (typeof bootstrap === 'undefined') {
document.write('<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"><\/script>');
}
</script>
<script>
$(function () {
if ($.fn.bootstrapTable && $.fn.bootstrapTable.locales) {
// Deutsch
if ($.fn.bootstrapTable.locales['de-DE']) {
$.fn.bootstrapTable.locales['de-DE'].formatColumnsToggle = function () {
return 'Spalten';
};
}
// Fallback: globaler Default (greift wenn Locale nicht explizit gesetzt)
$.extend($.fn.bootstrapTable.defaults, {
formatColumnsToggle: function () { return 'Spalten'; }
});
}
});
</script>
<script>
// Läuft nach jedem Table-Init und prüft ob der Columns-Button Einträge hat.
// Ist das Dropdown leer (z. B. in Modulen ohne data-show-columns-Konfiguration),
// wird der gesamte Button-Container versteckt.
$(document).on('init.bs.table', 'table', function () {
var $table = $(this);
// Kurze Verzögerung damit Bootstrap Table seinen DOM vollständig gerendert hat
setTimeout(function () {
var $btnGroup = $table.closest('.bootstrap-table')
.find('.columns-btn-group, .btn-group.columns');
if ($btnGroup.length > 0) {
// Prüfe ob der Dropdown-Inhalt tatsächlich Einträge hat
var itemCount = $btnGroup.find('li').length;
if (itemCount === 0) {
$btnGroup.hide();
}
}
}, 100);
});
</script>
<!-- Bootstrap Typeahead (lokal mit CDN-Fallback) -->
<script src="/libs/bootstrap3-typeahead/current/bootstrap3-typeahead.min.js"></script>
<script>
if (typeof $.fn.typeahead === 'undefined') {
document.write('<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-3-typeahead/4.0.2/bootstrap3-typeahead.min.js"><\/script>');
}
</script>
<!-- Bootstrap Table JS (lokal mit CDN-Fallback) -->
<script src="/libs/bootstrap-table/current/js/bootstrap-table.min.js"></script>
<script>
if (typeof $.fn.bootstrapTable === 'undefined') {
document.write('<script src="https://unpkg.com/bootstrap-table@1.21.4/dist/bootstrap-table.min.js"><\/script>');
document.write('<script src="https://unpkg.com/bootstrap-table@1.21.4/dist/locale/bootstrap-table-de-DE.min.js"><\/script>');
}
</script>
<script src="/libs/bootstrap-table/current/locale/bootstrap-table-de-DE.min.js"></script>
</head>
<body class="page-wrapper">
<!-- Site Header -->
<header class="site-header">
<div class="container">
<a href="index.php" class="site-logo">
<span class="site-title">BIBB Normdateienverwaltung</span>
</a>
<nav class="main-nav">
<ul>
<li><a href="index.php">Startseite</a></li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown">Normdateien</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="Subjects.php">Schlagworte</a></li>
<li><a class="dropdown-item" href="Persons.php">Personen</a></li>
<li><a class="dropdown-item" href="Corporates.php">Körperschaften</a></li>
<li><a class="dropdown-item" href="Publishers.php">Verlage</a></li>
<li><a class="dropdown-item" href="Classifications.php">Klassifikation</a></li>
<li><a class="dropdown-item" href="Treeview.php">Klassifikationsbaum</a></li>
</ul>
</li>
<li><a href="Statistics.php">Statistik</a></li>
</ul>
</nav>
</div>
</header>

View File

@ -0,0 +1,318 @@
<!-- Modal - Person Details mit semantischem Netz -->
<div class="modal fade" id="DetailsModal" tabindex="-1" aria-labelledby="DetailsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl" style="max-width: 1200px;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="DetailsModalHeadline">
<i class="fas fa-user"></i> Detailanzeige Person
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
</div>
<div class="modal-body">
<div class="row">
<!-- Linke Spalte: Stammdaten -->
<div class="col-md-4">
<div class="detail-info-panel">
<div class="form-group mb-3">
<label class="form-label fw-bold">Name</label>
<input type="text" id="display_term" class="form-control" disabled />
<input type="hidden" id="display_id" />
</div>
<div class="form-group mb-3">
<label class="form-label fw-bold">Deskriptor</label>
<input type="text" id="display_descriptor" class="form-control" disabled />
</div>
<div class="row mb-3">
<div class="col-6">
<label class="form-label fw-bold">Type</label>
<input type="text" id="display_type" class="form-control" disabled />
</div>
<div class="col-6">
<label class="form-label fw-bold">ID</label>
<input type="text" id="display_id_show" class="form-control" disabled />
</div>
</div>
<div class="form-group mb-3">
<label class="form-label fw-bold">Scopenote</label>
<textarea class="form-control" id="display_scopenote" rows="4" disabled></textarea>
</div>
</div>
</div>
<!-- Rechte Spalte: Semantisches Netz -->
<div class="col-md-8">
<div class="network-panel">
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="mb-0"><i class="fas fa-project-diagram me-2"></i>Semantisches Netz</h6>
<div class="network-toolbar">
<button class="btn btn-sm btn-outline-secondary" onclick="networkFit()" title="Ansicht anpassen">
<i class="fas fa-expand"></i>
</button>
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomIn()" title="Vergrößern">
<i class="fas fa-search-plus"></i>
</button>
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomOut()" title="Verkleinern">
<i class="fas fa-search-minus"></i>
</button>
</div>
</div>
<div id="network-container"></div>
<div class="network-legend">
<span class="legend-item"><span class="legend-dot" style="background: #1a3a5c;"></span> Zentrale Person</span>
<span class="legend-item"><span class="legend-dot" style="background: #28a745;"></span> Früherer Name</span>
<span class="legend-item"><span class="legend-dot" style="background: #dc3545;"></span> Späterer Name</span>
<span class="legend-item"><span class="legend-dot" style="background: #6c757d;"></span> USE/USEDFOR</span>
</div>
</div>
</div>
</div>
<!-- Versteckter Container für alte Relationen-Tabelle -->
<div class="modal_content" style="display: none;"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times"></i> schliessen
</button>
</div>
</div>
</div>
</div>
<style>
#network-container {
width: 100%;
height: 450px;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
}
.network-panel {
background: white;
border-radius: var(--radius-md);
padding: 15px;
border: 1px solid var(--border-color);
}
.detail-info-panel {
background: var(--bg-light);
border-radius: var(--radius-md);
padding: 15px;
border: 1px solid var(--border-color);
}
.network-toolbar {
display: flex;
gap: 5px;
}
.network-toolbar .btn {
padding: 4px 8px;
}
.network-legend {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid var(--border-light);
font-size: 12px;
}
.legend-item {
display: flex;
align-items: center;
gap: 5px;
}
.legend-dot {
width: 12px;
height: 12px;
border-radius: 50%;
display: inline-block;
}
#DetailsModal .modal-body {
max-height: 80vh;
overflow-y: auto;
}
</style>
<!-- vis.js Network Bibliothek -->
<script src="/libs/vis-network/current/vis-network.min.js"></script>
<script>if (typeof vis === "undefined") { document.write('<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"><\/script>'); }</script>
<script>
var network = null;
var networkData = null;
function createSemanticNetwork(centralTerm, centralId, relations) {
console.log('🕸️ Erstelle semantisches Netz für Person:', centralTerm);
var nodes = [];
var edges = [];
// Zentraler Knoten
nodes.push({
id: centralId,
label: wrapLabel(centralTerm, 20),
shape: 'ellipse',
color: {
background: '#1a3a5c',
border: '#0d1f30',
highlight: { background: '#2a5a8c', border: '#1a3a5c' },
hover: { background: '#2a5a8c', border: '#1a3a5c' }
},
font: { color: '#ffffff', size: 14, face: 'Arial', bold: true },
size: 35,
borderWidth: 3,
shadow: true
});
// Farben für Personen-Relationstypen
var relationColors = {
'FORMERNAME': { node: '#28a745', edge: '#28a745', label: 'Früherer Name' },
'LATERNAME': { node: '#dc3545', edge: '#dc3545', label: 'Späterer Name' },
'USE': { node: '#6c757d', edge: '#6c757d', label: 'Benutze' },
'USEDFOR': { node: '#6c757d', edge: '#6c757d', label: 'Benutzt für' }
};
if (relations && relations.length > 0) {
relations.forEach(function(rel, index) {
var relType = rel.Relationtype || 'USE';
var relColor = relationColors[relType] || { node: '#fd7e14', edge: '#fd7e14' };
var relId = rel.IDRelation || ('rel_' + index);
var relText = rel.TextRelation || 'Unbekannt';
nodes.push({
id: relId,
label: wrapLabel(relText, 18),
shape: 'ellipse',
color: {
background: relColor.node,
border: shadeColor(relColor.node, -20),
highlight: { background: shadeColor(relColor.node, 20), border: relColor.node },
hover: { background: shadeColor(relColor.node, 20), border: relColor.node }
},
font: { color: '#ffffff', size: 12, face: 'Arial' },
size: 25,
borderWidth: 2,
shadow: true,
title: relType + ': ' + relText + ' (ID: ' + relId + ')'
});
var arrowDirection = {};
if (relType === 'FORMERNAME') {
arrowDirection = { from: { enabled: true, scaleFactor: 1 } };
} else if (relType === 'LATERNAME') {
arrowDirection = { to: { enabled: true, scaleFactor: 1 } };
}
edges.push({
from: centralId,
to: relId,
label: relType,
color: { color: relColor.edge, highlight: relColor.edge, hover: relColor.edge },
width: 2,
arrows: arrowDirection,
font: { size: 10, color: '#666', strokeWidth: 3, strokeColor: '#ffffff' },
smooth: { type: 'curvedCW', roundness: 0.2 }
});
});
}
var container = document.getElementById('network-container');
networkData = {
nodes: new vis.DataSet(nodes),
edges: new vis.DataSet(edges)
};
var options = {
nodes: { shape: 'ellipse', scaling: { min: 20, max: 40 } },
edges: { smooth: { type: 'curvedCW', roundness: 0.2 } },
physics: {
enabled: true,
barnesHut: {
gravitationalConstant: -3000,
centralGravity: 0.3,
springLength: 150,
springConstant: 0.04,
damping: 0.09
},
stabilization: { enabled: true, iterations: 200, updateInterval: 25 }
},
interaction: { hover: true, tooltipDelay: 200, zoomView: true, dragView: true },
layout: { improvedLayout: true }
};
network = new vis.Network(container, networkData, options);
network.on('click', function(params) {
if (params.nodes.length > 0) {
var clickedNodeId = params.nodes[0];
if (clickedNodeId != centralId) {
ShowModalDetails(clickedNodeId);
}
}
});
network.once('stabilizationIterationsDone', function() {
network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
});
}
function wrapLabel(text, maxLength) {
if (!text) return '';
if (text.length <= maxLength) return text;
var words = text.split(' ');
var lines = [];
var currentLine = '';
words.forEach(function(word) {
if ((currentLine + ' ' + word).trim().length <= maxLength) {
currentLine = (currentLine + ' ' + word).trim();
} else {
if (currentLine) lines.push(currentLine);
currentLine = word;
}
});
if (currentLine) lines.push(currentLine);
return lines.join('\n');
}
function shadeColor(color, percent) {
var R = parseInt(color.substring(1,3), 16);
var G = parseInt(color.substring(3,5), 16);
var B = parseInt(color.substring(5,7), 16);
R = parseInt(R * (100 + percent) / 100);
G = parseInt(G * (100 + percent) / 100);
B = parseInt(B * (100 + percent) / 100);
R = (R < 255) ? R : 255;
G = (G < 255) ? G : 255;
B = (B < 255) ? B : 255;
var RR = ((R.toString(16).length == 1) ? "0" + R.toString(16) : R.toString(16));
var GG = ((G.toString(16).length == 1) ? "0" + G.toString(16) : G.toString(16));
var BB = ((B.toString(16).length == 1) ? "0" + B.toString(16) : B.toString(16));
return "#" + RR + GG + BB;
}
function networkFit() {
if (network) network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
}
function networkZoomIn() {
if (network) network.moveTo({ scale: network.getScale() * 1.3, animation: { duration: 300 } });
}
function networkZoomOut() {
if (network) network.moveTo({ scale: network.getScale() / 1.3, animation: { duration: 300 } });
}
</script>

View File

@ -0,0 +1 @@
<!-- PersonsModal - Platzhalter für zukünftige Erweiterungen -->

View File

@ -0,0 +1,317 @@
<!-- Modal - Verlag Details mit semantischem Netz -->
<div class="modal fade" id="DetailsModal" tabindex="-1" aria-labelledby="DetailsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl" style="max-width: 1200px;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="DetailsModalHeadline">
<i class="fas fa-book"></i> Detailanzeige Verlag
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
</div>
<div class="modal-body">
<div class="row">
<!-- Linke Spalte: Stammdaten -->
<div class="col-md-4">
<div class="detail-info-panel">
<div class="form-group mb-3">
<label class="form-label fw-bold">Name</label>
<input type="text" id="display_term" class="form-control" disabled />
<input type="hidden" id="display_id" />
</div>
<div class="form-group mb-3">
<label class="form-label fw-bold">Deskriptor</label>
<input type="text" id="display_descriptor" class="form-control" disabled />
</div>
<div class="row mb-3">
<div class="col-6">
<label class="form-label fw-bold">Type</label>
<input type="text" id="display_type" class="form-control" disabled />
</div>
<div class="col-6">
<label class="form-label fw-bold">ID</label>
<input type="text" id="display_id_show" class="form-control" disabled />
</div>
</div>
<div class="form-group mb-3">
<label class="form-label fw-bold">Scopenote</label>
<textarea class="form-control" id="display_scopenote" rows="4" disabled></textarea>
</div>
</div>
</div>
<!-- Rechte Spalte: Semantisches Netz -->
<div class="col-md-8">
<div class="network-panel">
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="mb-0"><i class="fas fa-project-diagram me-2"></i>Semantisches Netz</h6>
<div class="network-toolbar">
<button class="btn btn-sm btn-outline-secondary" onclick="networkFit()" title="Ansicht anpassen">
<i class="fas fa-expand"></i>
</button>
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomIn()" title="Vergrößern">
<i class="fas fa-search-plus"></i>
</button>
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomOut()" title="Verkleinern">
<i class="fas fa-search-minus"></i>
</button>
</div>
</div>
<div id="network-container"></div>
<div class="network-legend">
<span class="legend-item"><span class="legend-dot" style="background: #1a3a5c;"></span> Zentraler Verlag</span>
<span class="legend-item"><span class="legend-dot" style="background: #28a745;"></span> Früher verwendet</span>
<span class="legend-item"><span class="legend-dot" style="background: #dc3545;"></span> Später verwendet</span>
<span class="legend-item"><span class="legend-dot" style="background: #fd7e14;"></span> Alternativer Name</span>
<span class="legend-item"><span class="legend-dot" style="background: #6c757d;"></span> USE/USEDFOR</span>
</div>
</div>
</div>
</div>
<div class="modal_content" style="display: none;"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times"></i> schliessen
</button>
</div>
</div>
</div>
</div>
<style>
#network-container {
width: 100%;
height: 450px;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
}
.network-panel {
background: white;
border-radius: var(--radius-md);
padding: 15px;
border: 1px solid var(--border-color);
}
.detail-info-panel {
background: var(--bg-light);
border-radius: var(--radius-md);
padding: 15px;
border: 1px solid var(--border-color);
}
.network-toolbar {
display: flex;
gap: 5px;
}
.network-toolbar .btn {
padding: 4px 8px;
}
.network-legend {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid var(--border-light);
font-size: 12px;
}
.legend-item {
display: flex;
align-items: center;
gap: 5px;
}
.legend-dot {
width: 12px;
height: 12px;
border-radius: 50%;
display: inline-block;
}
#DetailsModal .modal-body {
max-height: 80vh;
overflow-y: auto;
}
</style>
<script src="/libs/vis-network/current/vis-network.min.js"></script>
<script>if (typeof vis === "undefined") { document.write('<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"><\/script>'); }</script>
<script>
var network = null;
var networkData = null;
function createSemanticNetwork(centralTerm, centralId, relations) {
console.log('🕸️ Erstelle semantisches Netz für Verlag:', centralTerm);
var nodes = [];
var edges = [];
nodes.push({
id: centralId,
label: wrapLabel(centralTerm, 20),
shape: 'ellipse',
color: {
background: '#1a3a5c',
border: '#0d1f30',
highlight: { background: '#2a5a8c', border: '#1a3a5c' },
hover: { background: '#2a5a8c', border: '#1a3a5c' }
},
font: { color: '#ffffff', size: 14, face: 'Arial', bold: true },
size: 35,
borderWidth: 3,
shadow: true
});
// Farben für Verlags-Relationstypen
var relationColors = {
'USEDEARLIER': { node: '#28a745', edge: '#28a745', label: 'Früher verwendet' },
'USEDLATER': { node: '#dc3545', edge: '#dc3545', label: 'Später verwendet' },
'ALTERNATIVENAME': { node: '#fd7e14', edge: '#fd7e14', label: 'Alternativer Name' },
'USE': { node: '#6c757d', edge: '#6c757d', label: 'Benutze' },
'USEDFOR': { node: '#6c757d', edge: '#6c757d', label: 'Benutzt für' }
};
if (relations && relations.length > 0) {
relations.forEach(function(rel, index) {
var relType = rel.Relationtype || 'USE';
var relColor = relationColors[relType] || { node: '#17a2b8', edge: '#17a2b8' };
var relId = rel.IDRelation || ('rel_' + index);
var relText = rel.TextRelation || 'Unbekannt';
nodes.push({
id: relId,
label: wrapLabel(relText, 18),
shape: 'ellipse',
color: {
background: relColor.node,
border: shadeColor(relColor.node, -20),
highlight: { background: shadeColor(relColor.node, 20), border: relColor.node },
hover: { background: shadeColor(relColor.node, 20), border: relColor.node }
},
font: { color: '#ffffff', size: 12, face: 'Arial' },
size: 25,
borderWidth: 2,
shadow: true,
title: relType + ': ' + relText + ' (ID: ' + relId + ')'
});
var arrowDirection = {};
if (relType === 'USEDEARLIER') {
arrowDirection = { from: { enabled: true, scaleFactor: 1 } };
} else if (relType === 'USEDLATER') {
arrowDirection = { to: { enabled: true, scaleFactor: 1 } };
}
edges.push({
from: centralId,
to: relId,
label: relType,
color: { color: relColor.edge, highlight: relColor.edge, hover: relColor.edge },
width: 2,
arrows: arrowDirection,
font: { size: 10, color: '#666', strokeWidth: 3, strokeColor: '#ffffff' },
smooth: { type: 'curvedCW', roundness: 0.2 }
});
});
}
var container = document.getElementById('network-container');
networkData = {
nodes: new vis.DataSet(nodes),
edges: new vis.DataSet(edges)
};
var options = {
nodes: { shape: 'ellipse', scaling: { min: 20, max: 40 } },
edges: { smooth: { type: 'curvedCW', roundness: 0.2 } },
physics: {
enabled: true,
barnesHut: {
gravitationalConstant: -3000,
centralGravity: 0.3,
springLength: 150,
springConstant: 0.04,
damping: 0.09
},
stabilization: { enabled: true, iterations: 200, updateInterval: 25 }
},
interaction: { hover: true, tooltipDelay: 200, zoomView: true, dragView: true },
layout: { improvedLayout: true }
};
network = new vis.Network(container, networkData, options);
network.on('click', function(params) {
if (params.nodes.length > 0) {
var clickedNodeId = params.nodes[0];
if (clickedNodeId != centralId) {
ShowModalDetails(clickedNodeId);
}
}
});
network.once('stabilizationIterationsDone', function() {
network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
});
}
function wrapLabel(text, maxLength) {
if (!text) return '';
if (text.length <= maxLength) return text;
var words = text.split(' ');
var lines = [];
var currentLine = '';
words.forEach(function(word) {
if ((currentLine + ' ' + word).trim().length <= maxLength) {
currentLine = (currentLine + ' ' + word).trim();
} else {
if (currentLine) lines.push(currentLine);
currentLine = word;
}
});
if (currentLine) lines.push(currentLine);
return lines.join('\n');
}
function shadeColor(color, percent) {
var R = parseInt(color.substring(1,3), 16);
var G = parseInt(color.substring(3,5), 16);
var B = parseInt(color.substring(5,7), 16);
R = parseInt(R * (100 + percent) / 100);
G = parseInt(G * (100 + percent) / 100);
B = parseInt(B * (100 + percent) / 100);
R = (R < 255) ? R : 255;
G = (G < 255) ? G : 255;
B = (B < 255) ? B : 255;
var RR = ((R.toString(16).length == 1) ? "0" + R.toString(16) : R.toString(16));
var GG = ((G.toString(16).length == 1) ? "0" + G.toString(16) : G.toString(16));
var BB = ((B.toString(16).length == 1) ? "0" + B.toString(16) : B.toString(16));
return "#" + RR + GG + BB;
}
function networkFit() {
if (network) network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
}
function networkZoomIn() {
if (network) network.moveTo({ scale: network.getScale() * 1.3, animation: { duration: 300 } });
}
function networkZoomOut() {
if (network) network.moveTo({ scale: network.getScale() / 1.3, animation: { duration: 300 } });
}
</script>

View File

@ -0,0 +1 @@
<!-- PublishersModal - Platzhalter für zukünftige Erweiterungen -->

View File

@ -0,0 +1,349 @@
<!-- Modal - Relationen tabellarisch -->
<div class="modal fade" id="RelationsModal" tabindex="-1" aria-labelledby="RelationsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" style="max-width: 900px;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="RelationsModalHeadline">
<i class="fas fa-table"></i> Relationen
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
</div>
<div class="modal-body">
<!-- Term-Info -->
<div class="relations-modal-info mb-3">
<div class="row">
<div class="col-md-6">
<label class="form-label fw-bold">Begriff</label>
<input type="text" id="relations_modal_term" class="form-control" disabled />
</div>
<div class="col-md-2">
<label class="form-label fw-bold">ID</label>
<input type="text" id="relations_modal_id" class="form-control" disabled />
</div>
<div class="col-md-2">
<label class="form-label fw-bold">Deskriptor</label>
<input type="text" id="relations_modal_descriptor" class="form-control" disabled />
</div>
<div class="col-md-2">
<label class="form-label fw-bold">DSpace</label>
<div id="relations_modal_dspace" class="form-control" style="background: #e9ecef;">
<span class="text-muted"></span>
</div>
</div>
</div>
</div>
<!-- Lade-Anzeige -->
<div id="relations_modal_loading" class="text-center py-4" style="display: none;">
<div class="spinner-border spinner-border-sm text-primary me-2" role="status"></div>
<span class="text-muted">Lade Relationen...</span>
</div>
<!-- Relationen-Tabelle -->
<div id="relations_modal_content" style="display: none;">
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
<table class="table table-sm table-hover table-bordered mb-0">
<thead class="table-light">
<tr>
<th style="width: 120px;">Typ</th>
<th>Begriff</th>
<th style="width: 80px;" class="text-center">ID</th>
</tr>
</thead>
<tbody id="relations_modal_body">
</tbody>
</table>
</div>
</div>
<!-- Keine Relationen -->
<div id="relations_modal_empty" class="text-center py-4 text-muted" style="display: none;">
<i class="fas fa-info-circle me-1"></i> Keine Relationen vorhanden.
</div>
<!-- Synonyme-Bereich (nur bei Subjects) -->
<div id="relations_modal_synonyms" style="display: none; margin-top: 15px;">
<h6 class="mb-2"><i class="fas fa-equals me-1" style="color: #e91e63;"></i> Synonyme</h6>
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
<table class="table table-sm table-hover table-bordered mb-0">
<thead class="table-light">
<tr>
<th style="width: 120px;">Typ</th>
<th>Begriff</th>
</tr>
</thead>
<tbody id="relations_modal_synonyms_body">
</tbody>
</table>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times"></i> schliessen
</button>
</div>
</div>
</div>
</div>
<style>
.relations-modal-info {
background: var(--bg-light, #f8f9fa);
border-radius: 8px;
padding: 15px;
border: 1px solid var(--border-color, #e0e0e0);
}
#RelationsModal .badge {
font-size: 0.8rem;
padding: 4px 8px;
min-width: 42px;
}
#RelationsModal .badge.bg-syn {
background-color: #e91e63;
color: #ffffff;
}
#RelationsModal .relation-link {
color: var(--primary-color, #1a3a5c);
text-decoration: none;
cursor: pointer;
}
#RelationsModal .relation-link:hover {
text-decoration: underline;
color: var(--primary-light, #2a5a8c);
}
#RelationsModal .modal-body {
max-height: 75vh;
overflow-y: auto;
}
</style>
<script>
/**
* Relationen-Modal öffnen und Daten laden
*/
function ShowRelationsModal(id, authType) {
console.log('📋 Lade Relationen für ID:', id, 'Typ:', authType);
// Reset
$('#relations_modal_term').val('');
$('#relations_modal_id').val(id);
$('#relations_modal_descriptor').val('');
$('#relations_modal_body').html('');
$('#relations_modal_synonyms_body').html('');
$('#relations_modal_content').hide();
$('#relations_modal_empty').hide();
$('#relations_modal_synonyms').hide();
$('#relations_modal_loading').show();
$('#RelationsModalHeadline').html('<i class="fas fa-table"></i> Relationen');
// Modal öffnen
$('#RelationsModal').modal('show');
// Daten laden
$.get("/Thesaurus/ajax/getDetailAuthorityData.php", {
authType: authType,
offset: 0,
id: id,
sort: 'Anchor.Text',
limit: 1
}, function(data, status) {
if (status === "success") {
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
var row = metadata['rows'];
if (!row || row.length === 0) {
$('#relations_modal_loading').hide();
$('#relations_modal_empty').show();
return;
}
var entry = row[0];
// Info-Felder befüllen
$('#relations_modal_term').val(entry.Text || '');
$('#relations_modal_id').val(entry.ID || id);
$('#relations_modal_descriptor').val(entry.Descriptor === false ? 'Nein' : 'Ja');
// DSpace-Count laden (nur bei Subject/Person und Deskriptor)
var $dspaceField = $('#relations_modal_dspace');
$dspaceField.html('<span class="text-muted"></span>');
if ((authType === 'Subject' || authType === 'Person') && entry.Descriptor !== false) {
$dspaceField.html('<span class="spinner-border spinner-border-sm text-muted" style="width: 12px; height: 12px;" role="status"></span>');
$.ajax({
url: '/Thesaurus/ajax/getDSpaceCount.php',
type: 'GET',
data: { authType: authType, query: entry.Text },
dataType: 'json',
timeout: 15000,
success: function(data) {
var count = (data && typeof data.count !== 'undefined') ? data.count : 0;
if (count === -1) {
$dspaceField.html('<span class="text-muted" title="DSpace nicht erreichbar"><i class="fas fa-exclamation-triangle"></i></span>');
} else if (count > 0) {
$dspaceField.html('<span class="badge" style="background-color: #1a3a5c;">' + count + ' Datensätze</span>');
} else {
$dspaceField.html('<span class="badge bg-danger">0</span>');
}
},
error: function() {
$dspaceField.html('<span class="text-muted"><i class="fas fa-exclamation-triangle"></i></span>');
}
});
}
// Badge-Konfiguration
var badgeConfig = {
'BT': { css: 'bg-success', title: 'BT - Oberbegriff' },
'NT': { css: 'bg-danger', title: 'NT - Unterbegriff' },
'RT': { css: 'bg-warning text-dark', title: 'RT - Verwandter Begriff' },
'USE': { css: 'bg-info', title: 'USE - Benutze' },
'UF': { css: 'bg-secondary', title: 'UF - Benutzt für' },
'USEDFOR': { css: 'bg-secondary', title: 'USEDFOR - Benutzt für' },
'FORMERNAME': { css: 'bg-success', title: 'Früherer Name' },
'LATERNAME': { css: 'bg-danger', title: 'Späterer Name' },
'USEDEARLIER': { css: 'bg-success', title: 'Früher verwendet' },
'USEDLATER': { css: 'bg-danger', title: 'Später verwendet' },
'ALTERNATIVENAME': { css: 'bg-warning text-dark', title: 'Alternativer Name' },
'SYN': { css: 'bg-syn', title: 'SYN - Synonym' }
};
// Relationen rendern
var relations = entry.Relations || [];
var html = '';
if (relations.length > 0) {
relations.forEach(function(rel) {
var relId = rel.IDRelation || '';
// Selbst-Relation überspringen
if (relId == id) return;
var relType = rel.Relationtype || 'RT';
var badge = badgeConfig[relType] || { css: 'bg-secondary', title: relType };
var relText = _escRelModal(rel.TextRelation || '');
var relId = rel.IDRelation || '';
html += '<tr>';
html += '<td><span class="badge ' + badge.css + '" title="' + badge.title + '">' + relType + '</span></td>';
html += '<td><a class="relation-link" href="javascript:void(0)" onclick="ShowRelationsModal(\'' + relId + '\', \'' + authType + '\')" title="Relationen anzeigen">' + relText + '</a></td>';
html += '<td class="text-center text-muted"><small>' + relId + '</small></td>';
html += '</tr>';
});
$('#relations_modal_body').html(html);
if (html.length > 0) {
$('#relations_modal_content').show();
} else {
$('#relations_modal_empty').show();
}
} else {
$('#relations_modal_empty').show();
}
// Synonyme (nur bei Subjects)
if (authType === 'Subject') {
var synonyms = entry.Synonyms || '';
// Falls leer, separat laden
if (!synonyms || synonyms.trim() === '') {
$.ajax({
url: "/Thesaurus/ajax/getSynonyms.php",
type: "GET",
data: { text: entry.Text },
dataType: "json",
success: function(synResponse) {
if (synResponse.success && synResponse.synonyms && synResponse.synonyms.length > 0) {
synonyms = synResponse.synonyms.map(function(s) { return s.text; }).join(', ');
_renderSynonymsInModal(synonyms, badgeConfig);
}
}
});
} else {
_renderSynonymsInModal(synonyms, badgeConfig);
}
}
$('#relations_modal_loading').hide();
}
}).fail(function() {
$('#relations_modal_loading').hide();
$('#relations_modal_empty').html('<i class="fas fa-exclamation-circle me-1 text-danger"></i> Fehler beim Laden der Relationen.').show();
});
}
function _renderSynonymsInModal(synonyms, badgeConfig) {
if (!synonyms || synonyms.trim() === '') return;
var synArray = synonyms.split(',').map(function(s) { return s.trim(); }).filter(function(s) { return s.length > 0; });
if (synArray.length === 0) return;
var badge = badgeConfig['SYN'];
var html = '';
synArray.forEach(function(synText) {
html += '<tr>';
html += '<td><span class="badge ' + badge.css + '" title="' + badge.title + '">SYN</span></td>';
html += '<td>' + _escRelModal(synText) + '</td>';
html += '</tr>';
});
$('#relations_modal_synonyms_body').html(html);
$('#relations_modal_synonyms').show();
}
function _escRelModal(text) {
if (!text) return '';
var div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
/**
* Text in die Zwischenablage kopieren (modern mit Fallback)
*/
function copyTermToClipboard(text, btnElement) {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(function() {
_showCopyFeedback(btnElement);
}).catch(function() {
_fallbackCopy(text, btnElement);
});
} else {
_fallbackCopy(text, btnElement);
}
}
function _fallbackCopy(text, btnElement) {
var tempInput = document.createElement("input");
tempInput.value = text;
document.body.appendChild(tempInput);
tempInput.select();
document.execCommand("copy");
document.body.removeChild(tempInput);
_showCopyFeedback(btnElement);
}
function _showCopyFeedback(btnElement) {
if (!btnElement) return;
var $btn = $(btnElement).closest('.copy-btn');
var $icon = $btn.find('i');
$icon.removeClass('fa-copy').addClass('fa-check');
$btn.addClass('btn-success').removeClass('btn-outline-secondary');
setTimeout(function() {
$icon.removeClass('fa-check').addClass('fa-copy');
$btn.removeClass('btn-success').addClass('btn-outline-secondary');
}, 1500);
}
</script>

View File

@ -0,0 +1,745 @@
<!-- Modal - Schlagwort Details mit semantischem Netz (2 Ebenen) -->
<div class="modal fade" id="DetailsModal" tabindex="-1" aria-labelledby="DetailsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl" style="max-width: 1400px;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="DetailsModalHeadline">
<i class="fas fa-tag"></i> Detailanzeige
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
</div>
<div class="modal-body">
<div class="row">
<!-- Linke Spalte: Stammdaten -->
<div class="col-md-3">
<div class="detail-info-panel">
<div class="form-group mb-3">
<label class="form-label fw-bold">Schlagwort</label>
<input type="text" id="display_term" class="form-control" disabled />
<input type="hidden" id="display_id" />
</div>
<div class="form-group mb-3">
<label class="form-label fw-bold">Deskriptor</label>
<input type="text" id="display_descriptor" class="form-control" disabled />
</div>
<div class="row mb-3">
<div class="col-6">
<label class="form-label fw-bold">Type</label>
<input type="text" id="display_type" class="form-control" disabled />
</div>
<div class="col-6">
<label class="form-label fw-bold">ID</label>
<input type="text" id="display_id_show" class="form-control" disabled />
</div>
</div>
<div class="form-group mb-3">
<label class="form-label fw-bold">Detailtype</label>
<input type="text" id="display_detailtype" class="form-control" disabled />
</div>
<div class="form-group mb-3">
<label class="form-label fw-bold">Klassifikation</label>
<input type="text" id="display_classification" class="form-control" disabled />
</div>
<div class="form-group mb-3">
<label class="form-label fw-bold">Scopenote</label>
<textarea class="form-control" id="display_scopenote" rows="3" disabled></textarea>
</div>
<div class="form-group mb-3">
<label class="form-label fw-bold"><i class="fas fa-equals me-1" style="color: #e91e63;"></i>Synonyme</label>
<textarea class="form-control" id="display_synonyms" rows="2" disabled style="font-size: 0.9rem;"></textarea>
</div>
<!-- Lade-Status -->
<div id="network-status" class="mt-3">
<small class="text-muted">
<i class="fas fa-circle-notch fa-spin me-1"></i>
<span id="status-text">Lade Netzwerk...</span>
</small>
</div>
</div>
</div>
<!-- Rechte Spalte: Semantisches Netz -->
<div class="col-md-9">
<div class="network-panel">
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="mb-0"><i class="fas fa-project-diagram me-2"></i>Semantisches Netz (2 Ebenen)</h6>
<div class="network-toolbar">
<button class="btn btn-sm btn-outline-secondary" onclick="networkFit()" title="Ansicht anpassen">
<i class="fas fa-expand"></i>
</button>
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomIn()" title="Vergrößern">
<i class="fas fa-search-plus"></i>
</button>
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomOut()" title="Verkleinern">
<i class="fas fa-search-minus"></i>
</button>
<button class="btn btn-sm btn-outline-secondary" onclick="toggleSecondLevel()" title="2. Ebene ein/aus" id="btn-level">
<i class="fas fa-layer-group"></i> <span id="btn-level-text">2</span>
</button>
<button class="btn btn-sm btn-outline-secondary" onclick="togglePhysics()" title="Physik/Dynamik ein/aus" id="btn-physics">
<i class="fas fa-atom"></i>
</button>
</div>
</div>
<div id="network-container"></div>
<div class="network-legend">
<span class="legend-item"><span class="legend-dot" style="background: #1a3a5c;"></span> Zentraler Begriff</span>
<span class="legend-item"><span class="legend-dot" style="background: #28a745;"></span> Oberbegriff (BT)</span>
<span class="legend-item"><span class="legend-dot" style="background: #dc3545;"></span> Unterbegriff (NT)</span>
<span class="legend-item"><span class="legend-dot" style="background: #fd7e14;"></span> Verwandt (RT)</span>
<span class="legend-item"><span class="legend-dot" style="background: #e91e63;"></span> Synonym (SYN)</span>
<span class="legend-item"><span class="legend-dot" style="background: #6c757d;"></span> USE/UF</span>
<span class="legend-item"><span class="legend-dot" style="background: #adb5bd; opacity: 0.7;"></span> 2. Ebene</span>
</div>
</div>
</div>
</div>
<!-- Versteckter Container für alte Relationen-Tabelle (falls benötigt) -->
<div class="modal_content" style="display: none;"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times"></i> schliessen
</button>
</div>
</div>
</div>
</div>
<style>
/* Netzwerk-Container - größer für 2 Ebenen */
#network-container {
width: 100%;
height: 550px;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
}
.network-panel {
background: white;
border-radius: var(--radius-md);
padding: 15px;
border: 1px solid var(--border-color);
}
.detail-info-panel {
background: var(--bg-light);
border-radius: var(--radius-md);
padding: 15px;
border: 1px solid var(--border-color);
}
.network-toolbar {
display: flex;
gap: 5px;
}
.network-toolbar .btn {
padding: 4px 8px;
}
.network-legend {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid var(--border-light);
font-size: 12px;
}
.legend-item {
display: flex;
align-items: center;
gap: 5px;
}
.legend-dot {
width: 12px;
height: 12px;
border-radius: 50%;
display: inline-block;
}
/* Modal extra breit für 2 Ebenen */
#DetailsModal .modal-body {
max-height: 85vh;
overflow-y: auto;
}
#network-status {
padding: 8px;
background: white;
border-radius: 4px;
border: 1px solid var(--border-light);
}
</style>
<!-- vis.js Network Bibliothek -->
<script src="/libs/vis-network/current/vis-network.min.js"></script>
<script>if (typeof vis === "undefined") { document.write('<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"><\/script>'); }</script>
<script>
var network = null;
var networkData = null;
var nodesDataSet = null;
var edgesDataSet = null;
var centralNodeId = null;
var firstLevelNodeIds = [];
var secondLevelNodeIds = [];
var secondLevelEdgeIds = [];
var secondLevelVisible = true;
var secondLevelLoaded = false;
var physicsEnabled = true;
// Farben für verschiedene Relationstypen
var relationColors = {
'BT': { node: '#28a745', edge: '#28a745', label: 'Oberbegriff' },
'BROADERTERM': { node: '#28a745', edge: '#28a745', label: 'Oberbegriff' },
'NT': { node: '#dc3545', edge: '#dc3545', label: 'Unterbegriff' },
'NARROWERTERM': { node: '#dc3545', edge: '#dc3545', label: 'Unterbegriff' },
'RT': { node: '#fd7e14', edge: '#fd7e14', label: 'Verwandt' },
'RELATEDTERM': { node: '#fd7e14', edge: '#fd7e14', label: 'Verwandt' },
'USE': { node: '#6c757d', edge: '#6c757d', label: 'Benutze' },
'UF': { node: '#6c757d', edge: '#6c757d', label: 'Benutzt für' },
'USEDFOR': { node: '#6c757d', edge: '#6c757d', label: 'Benutzt für' },
'SYN': { node: '#e91e63', edge: '#e91e63', label: 'Synonym' },
'SYNONYM': { node: '#e91e63', edge: '#e91e63', label: 'Synonym' }
};
function createSemanticNetwork(centralTerm, centralId, relations, synonyms) {
console.log('🕸️ Erstelle semantisches Netz (2 Ebenen) für:', centralTerm);
console.log('📝 Synonyme:', synonyms);
centralNodeId = centralId;
firstLevelNodeIds = [];
secondLevelNodeIds = [];
secondLevelEdgeIds = [];
secondLevelVisible = true;
secondLevelLoaded = false;
physicsEnabled = true;
// Buttons zurücksetzen
var btn = document.getElementById('btn-level');
var btnText = document.getElementById('btn-level-text');
if (btn) {
btn.classList.remove('btn-warning');
btn.classList.add('btn-outline-secondary');
}
if (btnText) btnText.textContent = '2';
var btnPhysics = document.getElementById('btn-physics');
if (btnPhysics) {
btnPhysics.classList.remove('btn-warning');
btnPhysics.classList.add('btn-outline-secondary');
}
updateStatus('Baue erste Ebene auf...');
// DataSets erstellen
nodesDataSet = new vis.DataSet();
edgesDataSet = new vis.DataSet();
// Zentraler Knoten
nodesDataSet.add({
id: centralId,
label: wrapLabel(centralTerm, 20),
shape: 'ellipse',
color: {
background: '#1a3a5c',
border: '#0d1f30',
highlight: { background: '#2a5a8c', border: '#1a3a5c' },
hover: { background: '#2a5a8c', border: '#1a3a5c' }
},
font: { color: '#ffffff', size: 14, face: 'Arial', bold: true },
size: 40,
borderWidth: 3,
shadow: true,
level: 0
});
// Erste Ebene hinzufügen
if (relations && relations.length > 0) {
relations.forEach(function(rel, index) {
var relType = rel.Relationtype || 'RT';
var relColor = relationColors[relType] || relationColors['RT'];
var relId = rel.IDRelation || ('rel_' + index);
var relText = rel.TextRelation || 'Unbekannt';
// ID für späteres Nachladen speichern
if (relId && relId !== centralId) {
firstLevelNodeIds.push({
id: relId,
text: relText,
type: relType
});
}
// Knoten für die Relation (1. Ebene)
nodesDataSet.add({
id: relId,
label: wrapLabel(relText, 18),
shape: 'ellipse',
color: {
background: relColor.node,
border: shadeColor(relColor.node, -20),
highlight: { background: shadeColor(relColor.node, 20), border: relColor.node },
hover: { background: shadeColor(relColor.node, 20), border: relColor.node }
},
font: { color: '#ffffff', size: 12, face: 'Arial' },
size: 28,
borderWidth: 2,
shadow: true,
title: relType + ': ' + relText + ' (ID: ' + relId + ')',
level: 1
});
// Kante zum zentralen Knoten
var arrowDirection = getArrowDirection(relType);
edgesDataSet.add({
from: centralId,
to: relId,
label: relType,
color: { color: relColor.edge, highlight: relColor.edge, hover: relColor.edge },
width: 2,
arrows: arrowDirection,
font: { size: 10, color: '#666', strokeWidth: 3, strokeColor: '#ffffff' },
smooth: { type: 'curvedCW', roundness: 0.2 }
});
});
}
// Synonyme hinzufügen (kommaseparierter String)
console.log('📝 Synonyme Typ:', typeof synonyms);
console.log('📝 Synonyme Wert:', synonyms);
console.log('📝 Synonyme Länge:', synonyms ? synonyms.length : 'null/undefined');
if (synonyms && typeof synonyms === 'string' && synonyms.trim().length > 0) {
var synColor = relationColors['SYN'];
var synArray = synonyms.split(',').map(function(s) { return s.trim(); }).filter(function(s) { return s.length > 0; });
console.log('📝 Synonym-Array:', synArray);
synArray.forEach(function(synText, index) {
var synId = 'syn_' + index + '_' + synText.replace(/\s+/g, '_');
console.log(' Füge Synonym hinzu:', synText, '→ ID:', synId);
// Knoten für das Synonym
nodesDataSet.add({
id: synId,
label: wrapLabel(synText, 18),
shape: 'ellipse',
color: {
background: synColor.node,
border: shadeColor(synColor.node, -20),
highlight: { background: shadeColor(synColor.node, 20), border: synColor.node },
hover: { background: shadeColor(synColor.node, 20), border: synColor.node }
},
font: { color: '#ffffff', size: 12, face: 'Arial' },
size: 28,
borderWidth: 2,
shadow: true,
title: 'Synonym: ' + synText,
level: 1,
isSynonym: true,
synonymText: synText
});
// Kante zum zentralen Knoten (bidirektional, keine Pfeile)
edgesDataSet.add({
from: centralId,
to: synId,
label: 'SYN',
color: { color: synColor.edge, highlight: synColor.edge, hover: synColor.edge },
width: 2,
arrows: { to: { enabled: false }, from: { enabled: false } },
font: { size: 10, color: '#666', strokeWidth: 3, strokeColor: '#ffffff' },
smooth: { type: 'curvedCW', roundness: 0.2 },
dashes: [5, 5]
});
});
console.log('✅ ' + synArray.length + ' Synonyme hinzugefügt');
} else {
console.log('⚠️ Keine Synonyme vorhanden oder leer');
}
// Netzwerk-Container
var container = document.getElementById('network-container');
// Netzwerk-Daten
networkData = {
nodes: nodesDataSet,
edges: edgesDataSet
};
// Optionen
var options = {
nodes: {
shape: 'ellipse',
scaling: { min: 15, max: 40 }
},
edges: {
smooth: { type: 'curvedCW', roundness: 0.2 }
},
physics: {
enabled: true,
barnesHut: {
gravitationalConstant: -4000,
centralGravity: 0.2,
springLength: 180,
springConstant: 0.03,
damping: 0.09
},
stabilization: {
enabled: true,
iterations: 300,
updateInterval: 25
}
},
interaction: {
hover: true,
tooltipDelay: 200,
zoomView: true,
dragView: true
},
layout: {
improvedLayout: true
}
};
// Netzwerk erstellen
network = new vis.Network(container, networkData, options);
// Klick auf Knoten
network.on('click', function(params) {
if (params.nodes.length > 0) {
var clickedNodeId = params.nodes[0];
if (clickedNodeId != centralNodeId) {
console.log('🖱️ Knoten geklickt:', clickedNodeId);
ShowModalDetails(clickedNodeId);
}
}
});
// Stabilisierung abgeschlossen - dann zweite Ebene laden
network.once('stabilizationIterationsDone', function() {
console.log('✅ Erste Ebene stabilisiert');
network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
// Zweite Ebene laden
setTimeout(function() {
loadSecondLevel();
}, 500);
});
}
function loadSecondLevel() {
if (firstLevelNodeIds.length === 0) {
updateStatus('Fertig - keine weiteren Relationen');
hideStatus();
return;
}
updateStatus('Lade 2. Ebene (0/' + firstLevelNodeIds.length + ')...');
var loadedCount = 0;
var totalCount = firstLevelNodeIds.length;
// Für jeden Knoten der ersten Ebene die Relationen laden
firstLevelNodeIds.forEach(function(nodeInfo, index) {
setTimeout(function() {
loadRelationsForNode(nodeInfo.id, nodeInfo.text, function() {
loadedCount++;
updateStatus('Lade 2. Ebene (' + loadedCount + '/' + totalCount + ')...');
if (loadedCount >= totalCount) {
secondLevelLoaded = true;
updateStatus('Fertig - ' + nodesDataSet.length + ' Begriffe geladen');
// Netzwerk neu stabilisieren
network.stabilize(100);
setTimeout(function() {
network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
hideStatus();
}, 1000);
}
});
}, index * 100); // Gestaffeltes Laden um Server nicht zu überlasten
});
}
function loadRelationsForNode(nodeId, nodeText, callback) {
$.get("/Thesaurus/ajax/getDetailAuthorityData.php", {
authType: 'Subject',
offset: 0,
id: nodeId,
sort: 'Anchor.Text',
limit: 1
}, function(data, status) {
if (status === "success") {
try {
var metadata = JSON.parse(data);
var row = metadata['rows'];
if (row && row[0] && row[0].Relations) {
row[0].Relations.forEach(function(rel) {
var relId = rel.IDRelation;
var relText = rel.TextRelation;
var relType = rel.Relationtype || 'RT';
// Nicht den zentralen Knoten oder bereits existierende Knoten
if (relId && relId != centralNodeId) {
var existingNode = nodesDataSet.get(relId);
if (!existingNode) {
// Neuer Knoten der 2. Ebene (kleiner, blasser)
var relColor = relationColors[relType] || relationColors['RT'];
var fadedColor = fadeColor(relColor.node, 0.6);
nodesDataSet.add({
id: relId,
label: wrapLabel(relText, 15),
shape: 'ellipse',
color: {
background: fadedColor,
border: shadeColor(fadedColor, -15),
highlight: { background: relColor.node, border: shadeColor(relColor.node, -20) },
hover: { background: relColor.node, border: shadeColor(relColor.node, -20) }
},
font: { color: '#ffffff', size: 10, face: 'Arial' },
size: 18,
borderWidth: 1,
shadow: false,
title: '2. Ebene - ' + relType + ': ' + relText + ' (ID: ' + relId + ')',
level: 2
});
// ID für Toggle speichern
secondLevelNodeIds.push(relId);
}
// Kante hinzufügen (wenn noch nicht vorhanden)
var edgeId = nodeId + '_' + relId;
var reverseEdgeId = relId + '_' + nodeId;
if (!edgesDataSet.get(edgeId) && !edgesDataSet.get(reverseEdgeId)) {
var relColor = relationColors[relType] || relationColors['RT'];
var arrowDirection = getArrowDirection(relType);
edgesDataSet.add({
id: edgeId,
from: nodeId,
to: relId,
color: { color: fadeColor(relColor.edge, 0.5), highlight: relColor.edge, hover: relColor.edge },
width: 1,
arrows: arrowDirection,
dashes: true,
smooth: { type: 'curvedCW', roundness: 0.3 }
});
// ID für Toggle speichern
secondLevelEdgeIds.push(edgeId);
}
}
});
}
} catch (e) {
console.warn('⚠️ Fehler beim Parsen für Node', nodeId, e);
}
}
if (callback) callback();
}).fail(function() {
console.warn('⚠️ AJAX-Fehler für Node', nodeId);
if (callback) callback();
});
}
function getArrowDirection(relType) {
if (relType === 'BT' || relType === 'BROADERTERM') {
return { to: { enabled: true, scaleFactor: 1 } };
} else if (relType === 'NT' || relType === 'NARROWERTERM') {
return { from: { enabled: true, scaleFactor: 1 } };
}
return { to: { enabled: false }, from: { enabled: false } };
}
function updateStatus(text) {
$('#status-text').text(text);
$('#network-status').show();
}
function hideStatus() {
setTimeout(function() {
$('#network-status').fadeOut();
}, 2000);
}
// Hilfsfunktion: Farbe verblassen
function fadeColor(color, opacity) {
var R = parseInt(color.substring(1,3), 16);
var G = parseInt(color.substring(3,5), 16);
var B = parseInt(color.substring(5,7), 16);
// Mit Weiß mischen für verblassten Effekt
R = Math.round(R + (255 - R) * (1 - opacity));
G = Math.round(G + (255 - G) * (1 - opacity));
B = Math.round(B + (255 - B) * (1 - opacity));
var RR = R.toString(16).padStart(2, '0');
var GG = G.toString(16).padStart(2, '0');
var BB = B.toString(16).padStart(2, '0');
return "#" + RR + GG + BB;
}
// Hilfsfunktion: Label umbrechen
function wrapLabel(text, maxLength) {
if (!text) return '';
if (text.length <= maxLength) return text;
var words = text.split(' ');
var lines = [];
var currentLine = '';
words.forEach(function(word) {
if ((currentLine + ' ' + word).trim().length <= maxLength) {
currentLine = (currentLine + ' ' + word).trim();
} else {
if (currentLine) lines.push(currentLine);
currentLine = word;
}
});
if (currentLine) lines.push(currentLine);
return lines.join('\n');
}
// Hilfsfunktion: Farbe aufhellen/abdunkeln
function shadeColor(color, percent) {
var R = parseInt(color.substring(1,3), 16);
var G = parseInt(color.substring(3,5), 16);
var B = parseInt(color.substring(5,7), 16);
R = parseInt(R * (100 + percent) / 100);
G = parseInt(G * (100 + percent) / 100);
B = parseInt(B * (100 + percent) / 100);
R = (R < 255) ? R : 255;
G = (G < 255) ? G : 255;
B = (B < 255) ? B : 255;
R = (R > 0) ? R : 0;
G = (G > 0) ? G : 0;
B = (B > 0) ? B : 0;
var RR = ((R.toString(16).length == 1) ? "0" + R.toString(16) : R.toString(16));
var GG = ((G.toString(16).length == 1) ? "0" + G.toString(16) : G.toString(16));
var BB = ((B.toString(16).length == 1) ? "0" + B.toString(16) : B.toString(16));
return "#" + RR + GG + BB;
}
// Netzwerk-Steuerung
function networkFit() {
if (network) {
network.fit({
animation: { duration: 500, easingFunction: 'easeInOutQuad' }
});
}
}
function networkZoomIn() {
if (network) {
var scale = network.getScale() * 1.3;
network.moveTo({ scale: scale, animation: { duration: 300 } });
}
}
function networkZoomOut() {
if (network) {
var scale = network.getScale() / 1.3;
network.moveTo({ scale: scale, animation: { duration: 300 } });
}
}
function toggleSecondLevel() {
if (!network || !secondLevelLoaded) {
console.log('⚠️ 2. Ebene noch nicht geladen');
return;
}
secondLevelVisible = !secondLevelVisible;
var btn = document.getElementById('btn-level');
var btnText = document.getElementById('btn-level-text');
if (secondLevelVisible) {
// 2. Ebene einblenden
secondLevelNodeIds.forEach(function(nodeId) {
nodesDataSet.update({ id: nodeId, hidden: false });
});
secondLevelEdgeIds.forEach(function(edgeId) {
edgesDataSet.update({ id: edgeId, hidden: false });
});
btn.classList.remove('btn-warning');
btn.classList.add('btn-outline-secondary');
btnText.textContent = '2';
console.log('✅ 2. Ebene eingeblendet');
} else {
// 2. Ebene ausblenden
secondLevelNodeIds.forEach(function(nodeId) {
nodesDataSet.update({ id: nodeId, hidden: true });
});
secondLevelEdgeIds.forEach(function(edgeId) {
edgesDataSet.update({ id: edgeId, hidden: true });
});
btn.classList.remove('btn-outline-secondary');
btn.classList.add('btn-warning');
btnText.textContent = '1';
console.log('✅ 2. Ebene ausgeblendet');
}
// Ansicht anpassen
setTimeout(function() {
network.fit({ animation: { duration: 300, easingFunction: 'easeInOutQuad' } });
}, 100);
}
function togglePhysics() {
if (!network) return;
physicsEnabled = !physicsEnabled;
network.setOptions({ physics: { enabled: physicsEnabled } });
var btn = document.getElementById('btn-physics');
if (physicsEnabled) {
btn.classList.remove('btn-warning');
btn.classList.add('btn-outline-secondary');
console.log('✅ Physik aktiviert');
} else {
btn.classList.remove('btn-outline-secondary');
btn.classList.add('btn-warning');
console.log('✅ Physik deaktiviert - Knoten können manuell verschoben werden');
}
}
</script>

View File

@ -0,0 +1,2 @@
<!-- Modal - Subjects Modal (für weitere Funktionen) -->
<!-- Dieses Template kann bei Bedarf erweitert werden -->

View File

@ -0,0 +1,111 @@
<!-- Modal - Neue Klassifikation -->
<div class="modal fade" id="NewClassificationModal" tabindex="-1" aria-labelledby="NewClassificationModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" style="max-width: 1024px;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="NewClassificationModalHeadline">
<i class="fas fa-folder-tree"></i> Klassifikation
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
</div>
<div class="modal-body">
<!-- Alert für Meldungen -->
<div class="row">
<div class="col-md-12">
<div id="errorNewClassification" class="alert" style="display: none;"></div>
</div>
</div>
<!-- Formular -->
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="new_notation">Notation</label>
<div class="col-sm-3">
<input type="text" id="new_notation" class="form-control" placeholder="z.B. 1.2.3" />
</div>
</div>
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="new_term">Bezeichnung</label>
<div class="col-sm-9">
<input type="text" id="new_term" class="form-control" />
<input type="hidden" id="new_id" />
</div>
</div>
<div class="form-group mb-3">
<label class="form-label" for="new_scopenote">Anmerkung</label>
<textarea class="form-control" id="new_scopenote" rows="3"></textarea>
</div>
<!-- Relationen-Bereich -->
<div id="new_relation_label" style="display: none;">
<hr>
<h5 class="mb-3"><i class="fas fa-link"></i> Relationen</h5>
<!-- Neue Relation hinzufügen -->
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="new_relationtype">Relationstyp</label>
<div class="col-sm-3">
<select class="form-select" id="new_relationtype">
<option value="BT">BT - Oberbegriff</option>
<option value="NT">NT - Unterbegriff</option>
<option value="RT">RT - Verwandter Begriff</option>
<option value="USE">USE - Benutze</option>
<option value="UF">UF - Benutzt für</option>
</select>
</div>
</div>
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="search_relation">Relation suchen</label>
<div class="col-sm-7">
<input type="text" id="search_relation" class="form-control" placeholder="Klassifikation eingeben..." />
</div>
<div class="col-sm-2">
<button type="button" class="btn btn-success w-100" id="classification-anlegen">
<i class="fas fa-plus"></i> Hinzufügen
</button>
</div>
</div>
<!-- Vorhandene Relationen -->
<div id="relation_table" style="display: none;">
<hr>
<h6><i class="fas fa-link me-1" style="color: #17a2b8;"></i> Vorhandene Relationen</h6>
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
<table class="table table-sm table-hover table-bordered mb-0">
<thead class="table-light">
<tr>
<th style="width: 80px;">Typ</th>
<th>Begriff</th>
<th style="width: 80px;" class="text-end">Aktion</th>
</tr>
</thead>
<tbody class="new_modal_content">
<!-- Relationen werden hier per AJAX eingefügt -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button id="newClassificationSave" type="button" class="btn btn-success" title="New Classification" onclick="CreateNewEntry('Classification')">
<i class="fas fa-save"></i> speichern
</button>
<button id="ClassificationModifySave" type="button" class="btn btn-success" style="display: none;" title="Modify Classification" onclick="UpdateEntry('Classification')">
<i class="fas fa-sync"></i> aktualisieren
</button>
<button id="newClassificationDismiss" type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times"></i> schliessen
</button>
<button id="newRelationClassificationDismiss" type="button" class="btn btn-secondary" style="display: none;" data-bs-dismiss="modal">
<i class="fas fa-times"></i> schliessen
</button>
</div>
</div>
</div>
</div>

115
templates/newCorporate.tpl Normal file
View File

@ -0,0 +1,115 @@
<!-- Modal - Neue Körperschaft -->
<div class="modal fade" id="NewCorporateModal" tabindex="-1" aria-labelledby="NewCorporateModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" style="max-width: 1024px;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="NewCorporateModalHeadline">
<i class="fas fa-building"></i> Körperschaft
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
</div>
<div class="modal-body">
<!-- Alert für Meldungen -->
<div class="row">
<div class="col-md-12">
<div id="errorNewCorporate" class="alert" style="display: none;"></div>
</div>
</div>
<!-- Formular -->
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="new_term">Name</label>
<div class="col-sm-9">
<input type="text" id="new_term" class="form-control" />
<input type="hidden" id="new_id" />
</div>
</div>
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="new_type">Type</label>
<div class="col-sm-3">
<select class="form-select" id="new_type">
<option value="Corporates">Körperschaft</option>
<option value="Conference">Konferenz</option>
<option value="Project">Projekt</option>
</select>
</div>
</div>
<div class="form-group mb-3">
<label class="form-label" for="new_scopenote">Anmerkung</label>
<textarea class="form-control" id="new_scopenote" rows="3"></textarea>
</div>
<!-- Relationen-Bereich -->
<div id="new_relation_label" style="display: none;">
<hr>
<h5 class="mb-3"><i class="fas fa-link"></i> Relationen</h5>
<!-- Neue Relation hinzufügen -->
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="new_relationtype">Relationstyp</label>
<div class="col-sm-3">
<select class="form-select" id="new_relationtype">
<option value="USEDEARLIER">Früher verwendet</option>
<option value="USEDLATER">Später verwendet</option>
<option value="ALTERNATIVENAME">Alternativer Name</option>
<option value="USE">Benutze</option>
<option value="USEDFOR">Benutzt für</option>
</select>
</div>
</div>
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="search_relation">Relation suchen</label>
<div class="col-sm-7">
<input type="text" id="search_relation" class="form-control" placeholder="Körperschaft eingeben..." />
</div>
<div class="col-sm-2">
<button type="button" class="btn btn-success w-100" id="corporate-anlegen">
<i class="fas fa-plus"></i> Hinzufügen
</button>
</div>
</div>
<!-- Vorhandene Relationen -->
<div id="relation_table" style="display: none;">
<hr>
<h6><i class="fas fa-link me-1" style="color: #17a2b8;"></i> Vorhandene Relationen</h6>
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
<table class="table table-sm table-hover table-bordered mb-0">
<thead class="table-light">
<tr>
<th style="width: 80px;">Typ</th>
<th>Begriff</th>
<th style="width: 80px;" class="text-end">Aktion</th>
</tr>
</thead>
<tbody class="new_modal_content">
<!-- Relationen werden hier per AJAX eingefügt -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button id="newCorporateSave" type="button" class="btn btn-success" title="New Corporate" onclick="CreateNewEntry('Corporates')">
<i class="fas fa-save"></i> speichern
</button>
<button id="CorporateModifySave" type="button" class="btn btn-success" style="display: none;" title="Modify Corporate" onclick="UpdateEntry('Corporate')">
<i class="fas fa-sync"></i> aktualisieren
</button>
<button id="newCorporateDismiss" type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times"></i> schliessen
</button>
<button id="newRelationCorporateDismiss" type="button" class="btn btn-secondary" style="display: none;" data-bs-dismiss="modal">
<i class="fas fa-times"></i> schliessen
</button>
</div>
</div>
</div>
</div>

103
templates/newPerson.tpl Normal file
View File

@ -0,0 +1,103 @@
<!-- Modal - Neue Person -->
<div class="modal fade" id="NewSubjectModal" tabindex="-1" aria-labelledby="NewSubjectModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" style="max-width: 1024px;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="NewSubjectModalHeadline">
<i class="fas fa-user"></i> Person
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
</div>
<div class="modal-body">
<!-- Alert für Meldungen -->
<div class="row">
<div class="col-md-12">
<div id="errorNewSubject" class="alert" style="display: none;"></div>
</div>
</div>
<!-- Formular -->
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="new_term">Name</label>
<div class="col-sm-9">
<input type="text" id="new_term" class="form-control" />
<input type="hidden" id="new_id" />
</div>
</div>
<div class="form-group mb-3">
<label class="form-label" for="new_scopenote">Anmerkung</label>
<textarea class="form-control" id="new_scopenote" rows="3"></textarea>
</div>
<!-- Relationen-Bereich -->
<div id="new_relation_label" style="display: none;">
<hr>
<h5 class="mb-3"><i class="fas fa-link"></i> Relationen</h5>
<!-- Neue Relation hinzufügen -->
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="new_relationtype">Relationstyp</label>
<div class="col-sm-3">
<select class="form-select" id="new_relationtype">
<option value="FORMERNAME">Früherer Name</option>
<option value="LATERNAME">Späterer Name</option>
<option value="USE">Benutze</option>
<option value="USEDFOR">Benutzt für</option>
</select>
</div>
</div>
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="search_relation">Relation suchen</label>
<div class="col-sm-7">
<input type="text" id="search_relation" class="form-control" placeholder="Name eingeben..." />
</div>
<div class="col-sm-2">
<button type="button" class="btn btn-success w-100" id="subject-anlegen">
<i class="fas fa-plus"></i> Hinzufügen
</button>
</div>
</div>
<!-- Vorhandene Relationen -->
<div id="relation_table" style="display: none;">
<hr>
<h6><i class="fas fa-link me-1" style="color: #17a2b8;"></i> Vorhandene Relationen</h6>
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
<table class="table table-sm table-hover table-bordered mb-0">
<thead class="table-light">
<tr>
<th style="width: 80px;">Typ</th>
<th>Begriff</th>
<th style="width: 80px;" class="text-end">Aktion</th>
</tr>
</thead>
<tbody class="new_modal_content">
<!-- Relationen werden hier per AJAX eingefügt -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button id="newSubjectSave" type="button" class="btn btn-success" title="New Person" onclick="CreateNewEntry('Person')">
<i class="fas fa-save"></i> speichern
</button>
<button id="SubjectModifySave" type="button" class="btn btn-success" style="display: none;" title="Modify Person" onclick="UpdateEntry('Person')">
<i class="fas fa-sync"></i> aktualisieren
</button>
<button id="newSubjectDismiss" type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times"></i> schliessen
</button>
<button id="newRelationSubjectDismiss" type="button" class="btn btn-secondary" style="display: none;" data-bs-dismiss="modal">
<i class="fas fa-times"></i> schliessen
</button>
</div>
</div>
</div>
</div>

114
templates/newPublisher.tpl Normal file
View File

@ -0,0 +1,114 @@
<!-- Modal - Neuer Verlag -->
<div class="modal fade" id="NewPublisherModal" tabindex="-1" aria-labelledby="NewPublisherModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" style="max-width: 1024px;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="NewPublisherModalHeadline">
<i class="fas fa-book"></i> Verlag
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
</div>
<div class="modal-body">
<!-- Alert für Meldungen -->
<div class="row">
<div class="col-md-12">
<div id="errorNewPublisher" class="alert" style="display: none;"></div>
</div>
</div>
<!-- Formular -->
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="new_term">Name</label>
<div class="col-sm-9">
<input type="text" id="new_term" class="form-control" />
<input type="hidden" id="new_id" />
</div>
</div>
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="new_type">Type</label>
<div class="col-sm-3">
<select class="form-select" id="new_type">
<option value="Publisher">Verlag</option>
<option value="Imprint">Imprint</option>
</select>
</div>
</div>
<div class="form-group mb-3">
<label class="form-label" for="new_scopenote">Anmerkung</label>
<textarea class="form-control" id="new_scopenote" rows="3"></textarea>
</div>
<!-- Relationen-Bereich -->
<div id="new_relation_label" style="display: none;">
<hr>
<h5 class="mb-3"><i class="fas fa-link"></i> Relationen</h5>
<!-- Neue Relation hinzufügen -->
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="new_relationtype">Relationstyp</label>
<div class="col-sm-3">
<select class="form-select" id="new_relationtype">
<option value="USEDEARLIER">Früher verwendet</option>
<option value="USEDLATER">Später verwendet</option>
<option value="ALTERNATIVENAME">Alternativer Name</option>
<option value="USE">Benutze</option>
<option value="USEDFOR">Benutzt für</option>
</select>
</div>
</div>
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="search_relation">Relation suchen</label>
<div class="col-sm-7">
<input type="text" id="search_relation" class="form-control" placeholder="Verlag eingeben..." />
</div>
<div class="col-sm-2">
<button type="button" class="btn btn-success w-100" id="publisher-anlegen">
<i class="fas fa-plus"></i> Hinzufügen
</button>
</div>
</div>
<!-- Vorhandene Relationen -->
<div id="relation_table" style="display: none;">
<hr>
<h6><i class="fas fa-link me-1" style="color: #17a2b8;"></i> Vorhandene Relationen</h6>
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
<table class="table table-sm table-hover table-bordered mb-0">
<thead class="table-light">
<tr>
<th style="width: 80px;">Typ</th>
<th>Begriff</th>
<th style="width: 80px;" class="text-end">Aktion</th>
</tr>
</thead>
<tbody class="new_modal_content">
<!-- Relationen werden hier per AJAX eingefügt -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button id="newPublisherSave" type="button" class="btn btn-success" title="New Publisher" onclick="CreateNewEntry('Publisher')">
<i class="fas fa-save"></i> speichern
</button>
<button id="PublisherModifySave" type="button" class="btn btn-success" style="display: none;" title="Modify Publisher" onclick="UpdateEntry('Publisher')">
<i class="fas fa-sync"></i> aktualisieren
</button>
<button id="newPublisherDismiss" type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times"></i> schliessen
</button>
<button id="newRelationPublisherDismiss" type="button" class="btn btn-secondary" style="display: none;" data-bs-dismiss="modal">
<i class="fas fa-times"></i> schliessen
</button>
</div>
</div>
</div>
</div>

157
templates/newSubject.tpl Normal file
View File

@ -0,0 +1,157 @@
<!-- Modal - Neues Schlagwort -->
<div class="modal fade" id="NewSubjectModal" tabindex="-1" aria-labelledby="NewSubjectModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" style="max-width: 1024px;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="NewSubjectModalHeadline">
<i class="fas fa-tag"></i> Schlagwort
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
</div>
<div class="modal-body">
<!-- Alert für Meldungen -->
<div class="row">
<div class="col-md-12">
<div id="errorNewSubject" class="alert" style="display: none;"></div>
</div>
</div>
<!-- Formular -->
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="new_term">Schlagwort</label>
<div class="col-sm-9">
<input type="text" id="new_term" class="form-control" />
<input type="hidden" id="new_id" />
</div>
</div>
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="new_type">Type</label>
<div class="col-sm-3">
<select class="form-select" id="new_type">
<option value="Subject">Schlagwort</option>
<option value="Person">Personenname</option>
<option value="Corporates">Körperschaft</option>
<option value="Publisher">Verlag</option>
<option value="Classification">Klassifikation</option>
</select>
</div>
</div>
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="new_detailtype">Detailtype</label>
<div class="col-sm-3">
<select class="form-select" id="new_detailtype">
<option value="Sachschlagwort">Sachschlagwort</option>
<option value="geografisches Schlagwort">geografisches Schlagwort</option>
<option value="Formschlagwort">Formschlagwort</option>
<option value="Zeitschlagwort">Zeitschlagwort</option>
</select>
</div>
</div>
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="new_classification">Klassifikation</label>
<div class="col-sm-9">
<input type="text" id="new_classification" class="form-control" />
</div>
</div>
<div class="form-group mb-3">
<label class="form-label" for="new_scopenote">Scopenote</label>
<textarea class="form-control" id="new_scopenote" rows="3"></textarea>
</div>
<!-- Relationen-Bereich -->
<div id="new_relation_label" style="display: none;">
<hr>
<h5 class="mb-3"><i class="fas fa-link"></i> Relationen</h5>
<!-- Neue Relation hinzufügen -->
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="new_relationtype">Relationstyp</label>
<div class="col-sm-3">
<select class="form-select" id="new_relationtype">
<option value="BT">BT - Oberbegriff</option>
<option value="NT">NT - Unterbegriff</option>
<option value="RT">RT - Verwandter Begriff</option>
<option value="USE">USE - Benutze</option>
<option value="UF">UF - Benutzt für</option>
<option value="SYN">SYN - Synonym</option>
</select>
</div>
</div>
<div class="form-group row mb-3">
<label class="col-sm-2 col-form-label" for="search_relation">Relation suchen</label>
<div class="col-sm-7">
<input type="text" id="search_relation" class="form-control" placeholder="Begriff eingeben..." />
</div>
<div class="col-sm-2">
<button type="button" class="btn btn-success w-100" id="subject-anlegen">
<i class="fas fa-plus"></i> Hinzufügen
</button>
</div>
</div>
<!-- Vorhandene Relationen -->
<div id="relation_table" style="display: none;">
<hr>
<h6><i class="fas fa-link me-1" style="color: #17a2b8;"></i> Vorhandene Relationen</h6>
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
<table class="table table-sm table-hover table-bordered mb-0">
<thead class="table-light">
<tr>
<th style="width: 80px;">Typ</th>
<th>Begriff</th>
<th style="width: 80px;" class="text-end">Aktion</th>
</tr>
</thead>
<tbody class="new_modal_content">
<!-- Relationen werden hier per AJAX eingefügt -->
</tbody>
</table>
</div>
</div>
<!-- Synonyme-Bereich -->
<div id="synonyms_section" style="display: none; margin-top: 20px;">
<hr>
<h6><i class="fas fa-equals me-1" style="color: #e91e63;"></i> Synonyme</h6>
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
<table class="table table-sm table-hover table-bordered mb-0" id="synonyms_table">
<thead class="table-light">
<tr>
<th style="width: 80px;">Typ</th>
<th>Begriff</th>
<th style="width: 80px;" class="text-end">Aktion</th>
</tr>
</thead>
<tbody id="synonyms_body">
<!-- Synonyme werden hier per AJAX eingefügt -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button id="newSubjectSave" type="button" class="btn btn-success" title="New Subject" onclick="CreateNewEntry('Subject')">
<i class="fas fa-save"></i> speichern
</button>
<button id="SubjectModifySave" type="button" class="btn btn-success" style="display: none;" title="Modify Subject" onclick="UpdateEntry('Subject')">
<i class="fas fa-sync"></i> aktualisieren
</button>
<button id="newSubjectDismiss" type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times"></i> schliessen
</button>
<button id="newRelationSubjectDismiss" type="button" class="btn btn-secondary" style="display: none;" data-bs-dismiss="modal">
<i class="fas fa-times"></i> schliessen
</button>
</div>
</div>
</div>
</div>