iüpdate
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { apiList, apiGet, apiDelete, apiUpdate, toast } from './api.js';
|
import { apiList, apiGet, apiDelete, apiUpdate, apiAction, toast } from './api.js';
|
||||||
|
|
||||||
function formatUsage(usage){
|
function formatUsage(usage){
|
||||||
if (!usage || !usage.total) return '';
|
if (!usage || !usage.total) return '';
|
||||||
@@ -109,18 +109,72 @@ async function openTemplateEditor(id){
|
|||||||
export async function loadList(resource){
|
export async function loadList(resource){
|
||||||
const el=document.getElementById(`view-${resource}`); if(!el) return;
|
const el=document.getElementById(`view-${resource}`); if(!el) return;
|
||||||
|
|
||||||
|
const label = resource.charAt(0).toUpperCase()+resource.slice(1);
|
||||||
el.innerHTML=`<div class='rounded-2xl border bg-white overflow-hidden'>
|
el.innerHTML=`<div class='rounded-2xl border bg-white overflow-hidden'>
|
||||||
<div class='px-4 py-2 border-b bg-gray-50 text-sm font-medium'>${resource.charAt(0).toUpperCase()+resource.slice(1)}</div>
|
<div class='px-4 py-2 border-b bg-gray-50 text-sm font-medium flex items-center gap-3'>
|
||||||
|
<span>${label}</span>
|
||||||
|
<div class='ms-auto flex items-center gap-2'>
|
||||||
|
<div class='flex items-center gap-1'>
|
||||||
|
<input id='filter-${resource}' class='input text-sm' placeholder='Suche Name/API' />
|
||||||
|
<button id='filter-${resource}-reset' class='btn' type='button' title='Suche zurücksetzen'>×</button>
|
||||||
|
</div>
|
||||||
|
<select id='sort-${resource}' class='input text-sm'>
|
||||||
|
<option value='created_asc'>Erstelldatum (aufsteigend)</option>
|
||||||
|
<option value='name_asc'>Name A–Z</option>
|
||||||
|
<option value='name_desc'>Name Z–A</option>
|
||||||
|
<option value='updated_desc'>Zuletzt bearbeitet</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div id='list-${resource}' class='divide-y'>Lade …</div></div>`;
|
<div id='list-${resource}' class='divide-y'>Lade …</div></div>`;
|
||||||
|
|
||||||
const data=await apiList(resource);
|
const data=await apiList(resource);
|
||||||
const list=el.querySelector(`#list-${resource}`);
|
const list=el.querySelector(`#list-${resource}`);
|
||||||
|
const filterInput=el.querySelector(`#filter-${resource}`);
|
||||||
|
const filterReset=el.querySelector(`#filter-${resource}-reset`);
|
||||||
|
const sortSelect=el.querySelector(`#sort-${resource}`);
|
||||||
|
|
||||||
if(!Array.isArray(data)||data.length===0){
|
if(!Array.isArray(data)||data.length===0){
|
||||||
list.innerHTML=`<div class='p-4 text-sm text-gray-500'>Keine Einträge</div>`;
|
list.innerHTML=`<div class='p-4 text-sm text-gray-500'>Keine Einträge</div>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sortDefault = (window.__listSortDefault || 'created_asc');
|
||||||
|
if (sortSelect) sortSelect.value = sortDefault;
|
||||||
|
|
||||||
|
function compareByName(a, b) {
|
||||||
|
const av = String(a?.name || '');
|
||||||
|
const bv = String(b?.name || '');
|
||||||
|
return av.localeCompare(bv, undefined, { numeric: true, sensitivity: 'base' });
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDate(value) {
|
||||||
|
const t = Date.parse(value || '');
|
||||||
|
return Number.isFinite(t) ? t : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortItems(items, key) {
|
||||||
|
const listCopy = items.slice();
|
||||||
|
if (key === 'name_asc') {
|
||||||
|
return listCopy.sort(compareByName);
|
||||||
|
}
|
||||||
|
if (key === 'name_desc') {
|
||||||
|
return listCopy.sort((a,b)=>compareByName(b,a));
|
||||||
|
}
|
||||||
|
if (key === 'updated_desc') {
|
||||||
|
return listCopy.sort((a,b)=>parseDate(b.updated_at) - parseDate(a.updated_at));
|
||||||
|
}
|
||||||
|
return listCopy.sort((a,b)=>parseDate(a.created_at) - parseDate(b.created_at));
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchesQuery(item, query) {
|
||||||
|
if (!query) return true;
|
||||||
|
const q = query.toLowerCase();
|
||||||
|
const name = String(item?.name || '').toLowerCase();
|
||||||
|
const api = resource === 'templates' ? String(item?.api_name || '').toLowerCase() : '';
|
||||||
|
return name.includes(q) || (api && api.includes(q));
|
||||||
|
}
|
||||||
|
|
||||||
function parentBadge(r,it){
|
function parentBadge(r,it){
|
||||||
if(r==='sections'&&it.template_id) return `<span class="chip"><span class="dot"></span> Template #${it.template_id}${it.template_name ? ' · '+esc(it.template_name) : ''}</span>`;
|
if(r==='sections'&&it.template_id) return `<span class="chip"><span class="dot"></span> Template #${it.template_id}${it.template_name ? ' · '+esc(it.template_name) : ''}</span>`;
|
||||||
if(r==='blocks'&&it.section_id) return `<span class="chip"><span class="dot"></span> Section #${it.section_id}${it.section_name ? ' · '+esc(it.section_name) : ''}</span>`;
|
if(r==='blocks'&&it.section_id) return `<span class="chip"><span class="dot"></span> Section #${it.section_id}${it.section_name ? ' · '+esc(it.section_name) : ''}</span>`;
|
||||||
@@ -128,7 +182,8 @@ export async function loadList(resource){
|
|||||||
return '<span class="chip"><span class="dot"></span> frei</span>';
|
return '<span class="chip"><span class="dot"></span> frei</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
list.innerHTML=data.map(item=>{
|
function render(items){
|
||||||
|
list.innerHTML=items.map(item=>{
|
||||||
const name = esc(item.name||'');
|
const name = esc(item.name||'');
|
||||||
const apiName = resource==='templates' ? esc(item.api_name||'') : '';
|
const apiName = resource==='templates' ? esc(item.api_name||'') : '';
|
||||||
const apiLine = (resource==='templates' && apiName) ? `<div class='text-xs text-slate-500'>API: ${apiName}</div>` : '';
|
const apiLine = (resource==='templates' && apiName) ? `<div class='text-xs text-slate-500'>API: ${apiName}</div>` : '';
|
||||||
@@ -159,8 +214,49 @@ export async function loadList(resource){
|
|||||||
</div>`;
|
</div>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
|
bindListHandlers(list, resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyFilter(){
|
||||||
|
const query = (filterInput?.value || '').trim();
|
||||||
|
const sortKey = sortSelect?.value || sortDefault;
|
||||||
|
const filtered = data.filter(item => matchesQuery(item, query));
|
||||||
|
const sorted = sortItems(filtered, sortKey);
|
||||||
|
render(sorted);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function persistSort(nextValue){
|
||||||
|
window.__listSortDefault = nextValue;
|
||||||
|
try {
|
||||||
|
await apiAction('account.settings.update', { method: 'POST', data: { list_sort: nextValue } });
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterInput) {
|
||||||
|
filterInput.addEventListener('input', applyFilter);
|
||||||
|
}
|
||||||
|
if (filterReset) {
|
||||||
|
filterReset.addEventListener('click', () => {
|
||||||
|
if (filterInput) {
|
||||||
|
filterInput.value = '';
|
||||||
|
filterInput.focus();
|
||||||
|
}
|
||||||
|
applyFilter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (sortSelect) {
|
||||||
|
sortSelect.addEventListener('change', () => {
|
||||||
|
const next = sortSelect.value || sortDefault;
|
||||||
|
persistSort(next);
|
||||||
|
applyFilter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
applyFilter();
|
||||||
|
|
||||||
// --- Editor öffnen (ANPASSUNG) -----------------------------------------
|
// --- Editor öffnen (ANPASSUNG) -----------------------------------------
|
||||||
list.querySelectorAll('[data-open]').forEach(b=>b.addEventListener('click', async ()=>{
|
function bindListHandlers(scope, resName){
|
||||||
|
scope.querySelectorAll('[data-open]').forEach(b=>b.addEventListener('click', async ()=>{
|
||||||
const [res,id]=b.dataset.open.split(':');
|
const [res,id]=b.dataset.open.split(':');
|
||||||
|
|
||||||
// Detail laden, um Name + aktuellen HTML/Content zu haben
|
// Detail laden, um Name + aktuellen HTML/Content zu haben
|
||||||
@@ -185,7 +281,7 @@ export async function loadList(resource){
|
|||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
// edit snippet
|
// edit snippet
|
||||||
list.querySelectorAll('[data-edit]').forEach(b=>b.addEventListener('click', async ()=>{
|
scope.querySelectorAll('[data-edit]').forEach(b=>b.addEventListener('click', async ()=>{
|
||||||
const [res, id] = b.dataset.edit.split(':');
|
const [res, id] = b.dataset.edit.split(':');
|
||||||
if (res === 'snippets') await openSnippetEditor(id);
|
if (res === 'snippets') await openSnippetEditor(id);
|
||||||
if (res === 'templates') await openTemplateEditor(id);
|
if (res === 'templates') await openTemplateEditor(id);
|
||||||
@@ -193,7 +289,7 @@ export async function loadList(resource){
|
|||||||
|
|
||||||
// preview
|
// preview
|
||||||
const prevDlg=document.getElementById('previewDialog'), prevFrame=document.getElementById('previewFrame');
|
const prevDlg=document.getElementById('previewDialog'), prevFrame=document.getElementById('previewFrame');
|
||||||
list.querySelectorAll('[data-preview]').forEach(b=>b.addEventListener('click', async ()=>{
|
scope.querySelectorAll('[data-preview]').forEach(b=>b.addEventListener('click', async ()=>{
|
||||||
const [res,id]=b.dataset.preview.split(':');
|
const [res,id]=b.dataset.preview.split(':');
|
||||||
const obj=await apiGet(res,id);
|
const obj=await apiGet(res,id);
|
||||||
const html=(obj?.html||obj?.content||'<em>(leer)</em)');
|
const html=(obj?.html||obj?.content||'<em>(leer)</em)');
|
||||||
@@ -202,7 +298,7 @@ export async function loadList(resource){
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// test send (templates only)
|
// test send (templates only)
|
||||||
list.querySelectorAll('[data-test]').forEach(b=>b.addEventListener('click', ()=>{
|
scope.querySelectorAll('[data-test]').forEach(b=>b.addEventListener('click', ()=>{
|
||||||
const id = Number(b.dataset.test || '0');
|
const id = Number(b.dataset.test || '0');
|
||||||
const nm = b.dataset.name || '';
|
const nm = b.dataset.name || '';
|
||||||
if (!id) {
|
if (!id) {
|
||||||
@@ -225,7 +321,7 @@ export async function loadList(resource){
|
|||||||
let pending=null;
|
let pending=null;
|
||||||
delCancel && (delCancel.onclick=()=>{pending=null;delDlg.close();});
|
delCancel && (delCancel.onclick=()=>{pending=null;delDlg.close();});
|
||||||
|
|
||||||
list.querySelectorAll('[data-del]').forEach(b=>b.addEventListener('click', async ()=>{
|
scope.querySelectorAll('[data-del]').forEach(b=>b.addEventListener('click', async ()=>{
|
||||||
const [res,id]=b.dataset.del.split(':'); const nm=b.dataset.name||'';
|
const [res,id]=b.dataset.del.split(':'); const nm=b.dataset.name||'';
|
||||||
let usage = null;
|
let usage = null;
|
||||||
try {
|
try {
|
||||||
@@ -237,7 +333,7 @@ export async function loadList(resource){
|
|||||||
delText && (delText.innerHTML=`Soll <strong>${nm || '(ohne Name)'} #${id}</strong> aus <strong>${res}</strong> wirklich gelöscht werden?<br><span class="text-rose-600">Achtung:</span> Kinder-Elemente werden <em>nicht</em> automatisch mit gelöscht.${usageWarn}`);
|
delText && (delText.innerHTML=`Soll <strong>${nm || '(ohne Name)'} #${id}</strong> aus <strong>${res}</strong> wirklich gelöscht werden?<br><span class="text-rose-600">Achtung:</span> Kinder-Elemente werden <em>nicht</em> automatisch mit gelöscht.${usageWarn}`);
|
||||||
delDlg.showModal();
|
delDlg.showModal();
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
delForm && (delForm.onsubmit=async(e)=>{
|
delForm && (delForm.onsubmit=async(e)=>{
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if(!pending) return delDlg.close();
|
if(!pending) return delDlg.close();
|
||||||
|
|||||||
@@ -276,6 +276,7 @@ function fillSettingsForm(settings) {
|
|||||||
settingsForm.editor_default.value = settings.editor_default || 'grapesjs';
|
settingsForm.editor_default.value = settings.editor_default || 'grapesjs';
|
||||||
}
|
}
|
||||||
window.__editorDefault = settings.editor_default || 'grapesjs';
|
window.__editorDefault = settings.editor_default || 'grapesjs';
|
||||||
|
window.__listSortDefault = settings.list_sort || 'created_asc';
|
||||||
state.rotate = { bridge: false, sender: false, external: false };
|
state.rotate = { bridge: false, sender: false, external: false };
|
||||||
refreshAdminTables(settings.bridge_setup?.tables || [], settings.bridge_tables || []);
|
refreshAdminTables(settings.bridge_setup?.tables || [], settings.bridge_tables || []);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ CREATE TABLE IF NOT EXISTS `customer_users` (
|
|||||||
`password_hash` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
`password_hash` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||||
`role` varchar(30) NOT NULL DEFAULT 'editor',
|
`role` varchar(30) NOT NULL DEFAULT 'editor',
|
||||||
`is_active` tinyint(1) NOT NULL DEFAULT 1,
|
`is_active` tinyint(1) NOT NULL DEFAULT 1,
|
||||||
|
`list_sort` varchar(32) DEFAULT NULL,
|
||||||
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
||||||
`updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
`updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
@@ -138,6 +139,7 @@ CREATE TABLE IF NOT EXISTS `emailtemplate_customer_users` (
|
|||||||
`password_hash` varchar(255) NOT NULL,
|
`password_hash` varchar(255) NOT NULL,
|
||||||
`role` varchar(30) NOT NULL DEFAULT 'editor',
|
`role` varchar(30) NOT NULL DEFAULT 'editor',
|
||||||
`is_active` tinyint(1) NOT NULL DEFAULT 1,
|
`is_active` tinyint(1) NOT NULL DEFAULT 1,
|
||||||
|
`list_sort` varchar(32) DEFAULT NULL,
|
||||||
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
||||||
`updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
`updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
|
|||||||
@@ -415,6 +415,8 @@ class ApiKernel
|
|||||||
if ($descCol && isset($r[$descCol])) $item['desc'] = $r[$descCol];
|
if ($descCol && isset($r[$descCol])) $item['desc'] = $r[$descCol];
|
||||||
if ($catCol && isset($r[$catCol])) $item['category'] = $r[$catCol];
|
if ($catCol && isset($r[$catCol])) $item['category'] = $r[$catCol];
|
||||||
if ($updCol && isset($r[$updCol])) $item['updated_at'] = $r[$updCol];
|
if ($updCol && isset($r[$updCol])) $item['updated_at'] = $r[$updCol];
|
||||||
|
$createdCol = $this->firstExisting($allCols, ['created_at', 'created', 'createdAt']);
|
||||||
|
if ($createdCol && isset($r[$createdCol])) $item['created_at'] = $r[$createdCol];
|
||||||
|
|
||||||
// Lade HTML und JSON aus den korrekten Spalten
|
// Lade HTML und JSON aus den korrekten Spalten
|
||||||
$htmlCol = $this->firstExisting($allCols, ['html', 'body', 'markup', 'content']);
|
$htmlCol = $this->firstExisting($allCols, ['html', 'body', 'markup', 'content']);
|
||||||
@@ -1749,6 +1751,8 @@ class ApiKernel
|
|||||||
$this->ensureRole($user, ['owner', 'admin']);
|
$this->ensureRole($user, ['owner', 'admin']);
|
||||||
$customerId = (int)($user['customer_id'] ?? 0);
|
$customerId = (int)($user['customer_id'] ?? 0);
|
||||||
$settings = $this->ensureSettingsTokens($customerId, $this->getCustomerSettings($customerId));
|
$settings = $this->ensureSettingsTokens($customerId, $this->getCustomerSettings($customerId));
|
||||||
|
$this->ensureAuthUserListSortColumn();
|
||||||
|
$settings['list_sort'] = $this->resolveUserListSort($user, $customerId);
|
||||||
$this->respond(['ok' => true, 'settings' => $settings]);
|
$this->respond(['ok' => true, 'settings' => $settings]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1759,21 +1763,30 @@ class ApiKernel
|
|||||||
$customerId = (int)($user['customer_id'] ?? 0);
|
$customerId = (int)($user['customer_id'] ?? 0);
|
||||||
if ($customerId <= 0) $this->fail('Customer context missing', null, 500);
|
if ($customerId <= 0) $this->fail('Customer context missing', null, 500);
|
||||||
|
|
||||||
$bridgeUrl = trim((string)($this->in['bridge_url'] ?? ''));
|
$settings = $this->getCustomerSettings($customerId);
|
||||||
$bridgeToken = trim((string)($this->in['bridge_token'] ?? ''));
|
$hasBridgeUrl = array_key_exists('bridge_url', $this->in);
|
||||||
$senderToken = trim((string)($this->in['sender_token'] ?? ''));
|
$hasBridgeToken = array_key_exists('bridge_token', $this->in);
|
||||||
$externalToken = trim((string)($this->in['external_api_token'] ?? ''));
|
$hasSenderToken = array_key_exists('sender_token', $this->in);
|
||||||
$editorDefault = strtolower(trim((string)($this->in['editor_default'] ?? '')));
|
$hasExternalToken = array_key_exists('external_api_token', $this->in);
|
||||||
|
$hasEditorDefault = array_key_exists('editor_default', $this->in);
|
||||||
|
$hasListSort = array_key_exists('list_sort', $this->in);
|
||||||
|
$hasBridgeTables = array_key_exists('bridge_tables', $this->in);
|
||||||
|
|
||||||
|
$bridgeUrl = $hasBridgeUrl ? trim((string)($this->in['bridge_url'] ?? '')) : (string)($settings['bridge_url'] ?? '');
|
||||||
|
$bridgeToken = $hasBridgeToken ? trim((string)($this->in['bridge_token'] ?? '')) : (string)($settings['bridge_token'] ?? '');
|
||||||
|
$senderToken = $hasSenderToken ? trim((string)($this->in['sender_token'] ?? '')) : (string)($settings['sender_token'] ?? '');
|
||||||
|
$externalToken = $hasExternalToken ? trim((string)($this->in['external_api_token'] ?? '')) : (string)($settings['external_api_token'] ?? '');
|
||||||
|
$editorDefault = $hasEditorDefault ? strtolower(trim((string)($this->in['editor_default'] ?? ''))) : strtolower((string)($settings['editor_default'] ?? ''));
|
||||||
|
$listSort = $hasListSort ? strtolower(trim((string)($this->in['list_sort'] ?? ''))) : '';
|
||||||
$rotateBridge = !empty($this->in['rotate_bridge_token']);
|
$rotateBridge = !empty($this->in['rotate_bridge_token']);
|
||||||
$rotateSender = !empty($this->in['rotate_sender_token']);
|
$rotateSender = !empty($this->in['rotate_sender_token']);
|
||||||
$rotateExternal = !empty($this->in['rotate_external_token']);
|
$rotateExternal = !empty($this->in['rotate_external_token']);
|
||||||
$bridgeTables = $this->normalizeBridgeTables($this->in['bridge_tables'] ?? []);
|
$bridgeTables = $hasBridgeTables ? $this->normalizeBridgeTables($this->in['bridge_tables'] ?? []) : ($settings['bridge_tables'] ?? []);
|
||||||
|
|
||||||
if ($bridgeUrl && !filter_var($bridgeUrl, FILTER_VALIDATE_URL)) {
|
if ($bridgeUrl && !filter_var($bridgeUrl, FILTER_VALIDATE_URL)) {
|
||||||
$this->fail('Ungültige Bridge-URL', null, 422);
|
$this->fail('Ungültige Bridge-URL', null, 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
$settings = $this->getCustomerSettings($customerId);
|
|
||||||
if ($rotateBridge || $bridgeToken === '') $bridgeToken = $this->generateToken();
|
if ($rotateBridge || $bridgeToken === '') $bridgeToken = $this->generateToken();
|
||||||
if ($rotateSender || $senderToken === '') $senderToken = $this->generateToken();
|
if ($rotateSender || $senderToken === '') $senderToken = $this->generateToken();
|
||||||
if ($rotateExternal || $externalToken === '') $externalToken = $this->generateToken();
|
if ($rotateExternal || $externalToken === '') $externalToken = $this->generateToken();
|
||||||
@@ -1781,6 +1794,9 @@ class ApiKernel
|
|||||||
if ($editorDefault !== '' && !in_array($editorDefault, ['grapesjs', 'craftjs'], true)) {
|
if ($editorDefault !== '' && !in_array($editorDefault, ['grapesjs', 'craftjs'], true)) {
|
||||||
$this->fail('Ungültiger Editor-Typ', null, 422);
|
$this->fail('Ungültiger Editor-Typ', null, 422);
|
||||||
}
|
}
|
||||||
|
if ($listSort !== '' && !in_array($listSort, ['created_asc', 'name_asc', 'name_desc', 'updated_desc'], true)) {
|
||||||
|
$this->fail('Ungültige Sortierung', null, 422);
|
||||||
|
}
|
||||||
|
|
||||||
$settings = $this->saveCustomerSettings($customerId, [
|
$settings = $this->saveCustomerSettings($customerId, [
|
||||||
'bridge_url' => $bridgeUrl,
|
'bridge_url' => $bridgeUrl,
|
||||||
@@ -1790,6 +1806,11 @@ class ApiKernel
|
|||||||
'editor_default' => $editorDefault ?: null,
|
'editor_default' => $editorDefault ?: null,
|
||||||
'bridge_tables' => $bridgeTables,
|
'bridge_tables' => $bridgeTables,
|
||||||
]);
|
]);
|
||||||
|
if ($hasListSort) {
|
||||||
|
$this->ensureAuthUserListSortColumn();
|
||||||
|
$this->updateUserListSort($user, $customerId, $listSort ?: null);
|
||||||
|
}
|
||||||
|
$settings['list_sort'] = $this->resolveUserListSort($user, $customerId, $listSort);
|
||||||
|
|
||||||
$this->respond(['ok' => true, 'settings' => $settings]);
|
$this->respond(['ok' => true, 'settings' => $settings]);
|
||||||
}
|
}
|
||||||
@@ -2734,6 +2755,100 @@ SQL;
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function ensureAuthUserListSortColumn(): void
|
||||||
|
{
|
||||||
|
if (!$this->pdo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$cols = $this->authUserColumns();
|
||||||
|
$table = $cols['table'];
|
||||||
|
try {
|
||||||
|
$columns = $this->tableColumns($table);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$this->fail('User-Tabelle konnte nicht gelesen werden', $e->getMessage(), 500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (in_array('list_sort', $columns, true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$sql = 'ALTER TABLE `' . $table . '` ADD COLUMN `list_sort` varchar(32) DEFAULT NULL';
|
||||||
|
$this->pdo->exec($sql);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$this->fail('User-Tabelle konnte nicht aktualisiert werden', $e->getMessage(), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getUserListSort(array $user, int $customerId): ?string
|
||||||
|
{
|
||||||
|
if (!$this->pdo) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$userId = (int)($user['id'] ?? 0);
|
||||||
|
if ($userId <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$cols = $this->authUserColumns();
|
||||||
|
$table = $cols['table'];
|
||||||
|
$dbCols = $this->tableColumns($table);
|
||||||
|
if (!$this->columnExists($dbCols, 'list_sort')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$where = sprintf('`%s` = :id', $cols['col_id']);
|
||||||
|
$params = [':id' => $userId];
|
||||||
|
if ($customerId > 0 && $this->columnExists($dbCols, $cols['col_customer'])) {
|
||||||
|
$where .= sprintf(' AND `%s` = :cid', $cols['col_customer']);
|
||||||
|
$params[':cid'] = $customerId;
|
||||||
|
}
|
||||||
|
$sql = sprintf('SELECT `list_sort` FROM `%s` WHERE %s LIMIT 1', $table, $where);
|
||||||
|
$stmt = $this->pdo->prepare($sql);
|
||||||
|
$stmt->execute($params);
|
||||||
|
$row = $stmt->fetch();
|
||||||
|
if (!$row) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $row['list_sort'] !== null ? (string)$row['list_sort'] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateUserListSort(array $user, int $customerId, ?string $value): void
|
||||||
|
{
|
||||||
|
if (!$this->pdo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$userId = (int)($user['id'] ?? 0);
|
||||||
|
if ($userId <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$cols = $this->authUserColumns();
|
||||||
|
$table = $cols['table'];
|
||||||
|
$dbCols = $this->tableColumns($table);
|
||||||
|
if (!$this->columnExists($dbCols, 'list_sort')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$where = sprintf('`%s` = :id', $cols['col_id']);
|
||||||
|
$params = [
|
||||||
|
':id' => $userId,
|
||||||
|
':value' => $value,
|
||||||
|
];
|
||||||
|
if ($customerId > 0 && $this->columnExists($dbCols, $cols['col_customer'])) {
|
||||||
|
$where .= sprintf(' AND `%s` = :cid', $cols['col_customer']);
|
||||||
|
$params[':cid'] = $customerId;
|
||||||
|
}
|
||||||
|
$sql = sprintf('UPDATE `%s` SET `list_sort` = :value WHERE %s LIMIT 1', $table, $where);
|
||||||
|
$stmt = $this->pdo->prepare($sql);
|
||||||
|
$stmt->execute($params);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveUserListSort(array $user, int $customerId, string $fallback = ''): string
|
||||||
|
{
|
||||||
|
$value = $fallback !== '' ? $fallback : (string)($this->getUserListSort($user, $customerId) ?? '');
|
||||||
|
$allowed = ['created_asc', 'name_asc', 'name_desc', 'updated_desc'];
|
||||||
|
if ($value === '' || !in_array($value, $allowed, true)) {
|
||||||
|
return 'created_asc';
|
||||||
|
}
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
private function ensureAuthUserHydrated(array $user): array
|
private function ensureAuthUserHydrated(array $user): array
|
||||||
{
|
{
|
||||||
$role = (string)($user['role'] ?? '');
|
$role = (string)($user['role'] ?? '');
|
||||||
|
|||||||
Reference in New Issue
Block a user