Skip to content

Commit b70bdd1

Browse files
fix(archives): adamsreview re-review batch — 15 findings post-fix regressions (#136)
Fifth adamsreview fix batch — addresses 15 of the 20 findings from the re-review (rev_01KRSME44NB8FEMR749FS5EQZA) that surfaced regressions introduced by the prior 4 fix batches. Grouped into 5 fix-group sub-agents: * Group AA — data integrity (F001+F002+F003) - F001: F008 sweep used wrong column 'archival_unit_id' for archive_unit_activities (correct: 'unit_id'). DELETE was silently failing with MySQL 1054, leaving orphan link rows after every archival_unit soft-delete. - F002: authorityDestroyAction now also sweeps archive_agent_relations (Phase 2 link table) clearing rows where the soft-deleted authority appears as agent_id OR related_id. Previously: ric.json kept emitting relations to deleted agents. - F003: removed 'place_id BIGINT UNSIGNED NULL' from ddlArchiveActivities() — fresh installs no longer create the column that migrate_0.7.12.sql then has to drop. Schema parity between fresh-install and upgrade paths restored. * Group BB — wire \$relations into show views (F004+F005) - activityShowAction + placeShowAction now fetch archive_relations via fetchRelationsForEntity() and pass to the render. The 'Relazioni RiC-CM' panel on the activity/place detail pages was always empty before; now displays actual relations. - try/catch around the fetch — DB failure logs but degrades to empty list rather than 500-ing the page. * Group CC — 15 missing locale keys (F009) - validateActivity/validatePlace error strings + F041 regex hints + F039 date placeholder were wrapped in __() but ABSENT from en_US/fr_FR/de_DE locale JSONs (commit d426f26 claimed 12 keys added; actually only 1 made it). - Added 15 keys per locale (60 lines total) with real translations in EN/FR/DE. Project i18n BLOCKING rule now satisfied. * Group DD — view UX fixes (F006+F007+F008+F012) - F006: attach/detach forms on activities/show.php + places/show.php now include 'name=_return_to' hidden input so safeReturnTo() redirects back to the originating detail page instead of the main /admin/archives index. - F007: target_id input on attach forms is now a typeahead bound to /api/archives/entities autocomplete (debounced 200ms, XSS-safe via textContent, click-to-select, clears on target_type change, submit-block when no id selected). Pattern mirrors the canonical archives/show.php. - F008: detach buttons now use archivesSwalConfirm() SweetAlert2 helper (already defined on the page) instead of native confirm(). Consistent with the entity-delete buttons. - F012: geonames_id / wikidata_id / tgn_id inputs on places/form.php now apply conditional border-red-500 + render error message when F041 validation rejects. * Group EE — validation cleanups + tests (F010+F011+F015+F016+F017) - F010: all 4 destroyAction handlers (archival_unit / authority / activity / place) wrapped in mysqli transactions with nested- transaction safety via @@autocommit detection. Soft-delete UPDATE + Phase 2/3/4 sweep DELETEs now succeed or fail atomically. - F011: inline comment on archive_unit_activities mirror in relationAttachAction documenting that predicate direction is preserved verbatim (canonical RiC-O inverse list deferred to v0.7.13). - F015: placeWouldCreateCycle now prepares the SELECT once outside the while loop instead of re-preparing every iteration (mirrors activityWouldCreateCycle's pattern). - F016: dropped 'AND deleted_at IS NULL' from placeWouldCreateCycle's SELECT — integrity walks must see the full topology (matches F018 fix already applied to activityWouldCreateCycle). - F017: 5 new unit tests in tests/archives-ric-jsonld.unit.php covering buildRelationNode + @graph composition (167 → 172). PHPStan level 5 clean. Unit tests 172/172. Smoke: ric.json 200, OAI ric-o 200, admin 302→login. With this commit the re-review cycle resolves the 15 highest-priority follow-ups. The 5 remaining below_gate findings (F013, F014, F018, F019, F020 — score < 45) are documented in trace.md for future cleanup.
1 parent f564a2d commit b70bdd1

9 files changed

Lines changed: 567 additions & 145 deletions

File tree

locale/de_DE.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,7 @@
576576
"Cerca per ISBN-13": "Nach ISBN-13 suchen",
577577
"Cerca per codice EAN": "Nach EAN-Code suchen",
578578
"Cerca per nome autore (corrispondenza parziale)": "Nach Autorenname suchen (Teilübereinstimmung)",
579+
"Cerca per nome o ID...": "Nach Name oder ID suchen...",
579580
"Cerca per nome, cognome, telefono, email o tessera": "Nach Name, Nachname, Telefon, E-Mail oder Ausweis suchen",
580581
"Cerca per nome...": "Nach Name suchen...",
581582
"Cerca per pseudonimo...": "Nach Pseudonym suchen...",

locale/en_US.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,7 @@
576576
"Cerca per ISBN-13": "Search by ISBN-13",
577577
"Cerca per codice EAN": "Search by EAN code",
578578
"Cerca per nome autore (corrispondenza parziale)": "Search by author name (partial match)",
579+
"Cerca per nome o ID...": "Search by name or ID...",
579580
"Cerca per nome, cognome, telefono, email o tessera": "Search by name, surname, phone, email or card",
580581
"Cerca per nome...": "Search by name...",
581582
"Cerca per pseudonimo...": "Search by pseudonym...",

locale/fr_FR.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,7 @@
608608
"Cerca per ISBN-13": "Rechercher par ISBN-13",
609609
"Cerca per codice EAN": "Rechercher par code EAN",
610610
"Cerca per nome autore (corrispondenza parziale)": "Rechercher par nom d'auteur (correspondance partielle)",
611+
"Cerca per nome o ID...": "Rechercher par nom ou ID...",
611612
"Cerca per nome, cognome, telefono, email o tessera": "Rechercher par nom, prénom, téléphone, e-mail ou carte",
612613
"Cerca per nome...": "Rechercher par nom...",
613614
"Cerca per pseudonimo...": "Rechercher par pseudonyme...",

locale/it_IT.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"Catalogo pubblico": "Catalogo pubblico",
7171
"Cerca": "Cerca",
7272
"Cerca \"%s\" su %s": "Cerca \"%s\" su %s",
73+
"Cerca per nome o ID...": "Cerca per nome o ID...",
7374
"Cerca su:": "Cerca su:",
7475
"Cerca un authority record…": "Cerca un authority record…",
7576
"Chiave Google Books rimossa.": "Chiave Google Books rimossa.",

storage/plugins/archives/ArchivesPlugin.php

Lines changed: 266 additions & 132 deletions
Large diffs are not rendered by default.

storage/plugins/archives/views/activities/show.php

Lines changed: 119 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,16 @@ class="bg-red-50 hover:bg-red-100 text-red-700 px-4 py-2 rounded text-sm font-se
171171
$tType = (string) ($rel['target_type'] ?? '');
172172
$tId = (int) ($rel['target_id'] ?? 0);
173173
?>
174+
<?php $relDetachId = 'archivesDetachRel_' . $relId; ?>
174175
<li class="flex items-center gap-2">
175176
<code class="text-xs text-gray-500 bg-gray-50 px-1 py-0.5 rounded"><?= $e($pred) ?></code>
176177
<span><?= $e($tType) ?> #<?= $tId ?></span>
177-
<form method="POST" action="<?= $e(url('/admin/archives/relations/' . $relId . '/detach')) ?>" class="inline">
178+
<form id="<?= $e($relDetachId) ?>" method="POST" action="<?= $e(url('/admin/archives/relations/' . $relId . '/detach')) ?>" class="inline">
178179
<input type="hidden" name="csrf_token" value="<?= $e(\App\Support\Csrf::ensureToken()) ?>">
179-
<button type="submit" onclick="return confirm('<?= $e(__('Scollegare questa relazione?')) ?>')" class="text-red-600 text-xs hover:underline">
180+
<input type="hidden" name="_return_to" value="<?= $e('/admin/archives/activities/' . $id) ?>">
181+
<button type="button"
182+
onclick="archivesSwalConfirm(<?= $jsAttr($relDetachId) ?>, <?= $jsAttr(__('Scollegare questa relazione?')) ?>, <?= $jsAttr(__('scollega')) ?>)"
183+
class="text-red-600 text-xs hover:underline">
180184
<?= __('scollega') ?>
181185
</button>
182186
</form>
@@ -185,18 +189,29 @@ class="bg-red-50 hover:bg-red-100 text-red-700 px-4 py-2 rounded text-sm font-se
185189
</ul>
186190
<?php endif; ?>
187191

188-
<form method="POST" action="<?= $e(url('/admin/archives/relations/attach')) ?>" class="mt-4 grid grid-cols-1 md:grid-cols-4 gap-2 text-sm">
192+
<form method="POST" action="<?= $e(url('/admin/archives/relations/attach')) ?>"
193+
class="mt-4 grid grid-cols-1 md:grid-cols-4 gap-2 text-sm"
194+
data-archives-relation-attach>
189195
<input type="hidden" name="csrf_token" value="<?= $e(\App\Support\Csrf::ensureToken()) ?>">
190196
<input type="hidden" name="source_type" value="archive_activity">
191197
<input type="hidden" name="source_id" value="<?= $id ?>">
192-
<select name="target_type" required class="rounded-md border-gray-300">
198+
<input type="hidden" name="_return_to" value="<?= $e('/admin/archives/activities/' . $id) ?>">
199+
<select name="target_type" id="rel_target_type" required class="rounded-md border-gray-300">
193200
<option value=""><?= __('Tipo entità') ?></option>
194201
<option value="archival_unit"><?= __('Unità archivistica') ?></option>
195202
<option value="authority_record"><?= __('Agente') ?></option>
196203
<option value="archive_activity"><?= __('Attività') ?></option>
197204
<option value="archive_place"><?= __('Luogo') ?></option>
198205
</select>
199-
<input type="number" name="target_id" required placeholder="<?= $e(__('ID target')) ?>" class="rounded-md border-gray-300">
206+
<div class="relative">
207+
<input type="text" id="rel_target_search" autocomplete="off"
208+
data-typeahead-input data-typeahead-target="rel_target_id"
209+
placeholder="<?= $e(__('Cerca per nome o ID...')) ?>"
210+
class="rounded-md border-gray-300 w-full">
211+
<input type="hidden" id="rel_target_id" name="target_id" required>
212+
<div data-typeahead-results
213+
class="absolute z-10 w-full bg-white border border-gray-200 rounded mt-1 shadow-lg hidden max-h-60 overflow-y-auto"></div>
214+
</div>
200215
<input type="text" name="ric_predicate" required placeholder="ric:isOrWasRelatedTo" class="rounded-md border-gray-300">
201216
<button type="submit" class="bg-indigo-600 hover:bg-indigo-700 text-white px-3 py-1 rounded text-sm font-semibold"><?= __('Collega') ?></button>
202217
</form>
@@ -211,6 +226,105 @@ class="bg-red-50 hover:bg-red-100 text-red-700 px-4 py-2 rounded text-sm font-se
211226
</p>
212227
</div>
213228

229+
<?php /* Relation-attach typeahead — fetches /api/archives/entities?type=&q=
230+
and populates the visible/hidden input pair. Pattern mirrors
231+
storage/plugins/archives/views/show.php (data-typeahead-*). */ ?>
232+
<script>
233+
(function () {
234+
var form = document.querySelector('form[data-archives-relation-attach]');
235+
if (!form) return;
236+
var input = form.querySelector('[data-typeahead-input]');
237+
var results = form.querySelector('[data-typeahead-results]');
238+
var hidden = document.getElementById(input.dataset.typeaheadTarget);
239+
var typeSel = form.querySelector('select[name="target_type"]');
240+
if (!input || !results || !hidden || !typeSel) return;
241+
var searchUrl = <?= json_encode(url('/api/archives/entities'), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
242+
var noResultsMsg = <?= json_encode(__('Nessun risultato'), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
243+
var debounceTimer = null;
244+
var lastQuery = '';
245+
246+
function clearResults() { while (results.firstChild) results.removeChild(results.firstChild); }
247+
function hideResults() { results.classList.add('hidden'); clearResults(); }
248+
249+
function renderResults(rows) {
250+
clearResults();
251+
if (!rows.length) {
252+
var empty = document.createElement('div');
253+
empty.className = 'px-3 py-2 text-gray-500 italic text-sm';
254+
empty.textContent = noResultsMsg;
255+
results.appendChild(empty);
256+
results.classList.remove('hidden');
257+
return;
258+
}
259+
for (var i = 0; i < rows.length; i++) {
260+
var r = rows[i];
261+
var div = document.createElement('div');
262+
div.className = 'px-3 py-2 cursor-pointer hover:bg-blue-50 text-sm';
263+
div.dataset.id = String(r.id);
264+
var label = r.label || ('#' + r.id);
265+
if (r.extra) { label += ' (' + r.extra + ')'; }
266+
div.dataset.label = label;
267+
div.textContent = label;
268+
results.appendChild(div);
269+
}
270+
results.classList.remove('hidden');
271+
}
272+
273+
function search(q, type) {
274+
if (!type) { hideResults(); return; }
275+
if (q.length < 2) { lastQuery = ''; hideResults(); return; }
276+
var key = type + '|' + q;
277+
if (key === lastQuery) return;
278+
lastQuery = key;
279+
var snapshot = key;
280+
fetch(searchUrl + '?type=' + encodeURIComponent(type) + '&q=' + encodeURIComponent(q), { credentials: 'same-origin' })
281+
.then(function (r) { return r.ok ? r.json() : { results: [] }; })
282+
.then(function (data) {
283+
if ((typeSel.value + '|' + input.value.trim()) !== snapshot) return;
284+
renderResults(data.results || []);
285+
})
286+
.catch(function () {
287+
if ((typeSel.value + '|' + input.value.trim()) === snapshot) hideResults();
288+
});
289+
}
290+
291+
input.addEventListener('input', function () {
292+
clearTimeout(debounceTimer);
293+
var q = input.value.trim();
294+
hidden.value = '';
295+
debounceTimer = setTimeout(function () { search(q, typeSel.value); }, 200);
296+
});
297+
298+
typeSel.addEventListener('change', function () {
299+
hidden.value = '';
300+
input.value = '';
301+
lastQuery = '';
302+
hideResults();
303+
});
304+
305+
results.addEventListener('click', function (ev) {
306+
var div = ev.target.closest('[data-id]');
307+
if (!div) return;
308+
hidden.value = div.dataset.id;
309+
input.value = div.dataset.label;
310+
hideResults();
311+
});
312+
313+
document.addEventListener('click', function (ev) {
314+
if (!form.contains(ev.target)) hideResults();
315+
});
316+
317+
form.addEventListener('submit', function (ev) {
318+
if (!hidden.value) {
319+
ev.preventDefault();
320+
input.focus();
321+
input.classList.add('border-red-400');
322+
setTimeout(function () { input.classList.remove('border-red-400'); }, 1500);
323+
}
324+
});
325+
})();
326+
</script>
327+
214328
<?php /* SweetAlert2 confirm helper — matches the pattern used in
215329
storage/plugins/archives/views/show.php. Idempotency guard so
216330
multiple views on the same page can load it without redefining. */ ?>

storage/plugins/archives/views/places/form.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,19 +128,28 @@ class="mt-1 block w-full rounded-md <?= $err('longitude') ? 'border-red-500' : '
128128
<label for="geonames_id" class="form-label"><?= __('Identificativo GeoNames') ?></label>
129129
<input type="text" id="geonames_id" name="geonames_id" value="<?= $val('geonames_id') ?>"
130130
placeholder="2525068"
131-
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
131+
class="mt-1 block w-full rounded-md <?= $err('geonames_id') ? 'border-red-500' : 'border-gray-300' ?> shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
132+
<?php if ($err('geonames_id')): ?>
133+
<p class="mt-1 text-sm text-red-600"><?= $e($err('geonames_id')) ?></p>
134+
<?php endif; ?>
132135
</div>
133136
<div>
134137
<label for="wikidata_id" class="form-label"><?= __('Identificativo Wikidata') ?></label>
135138
<input type="text" id="wikidata_id" name="wikidata_id" value="<?= $val('wikidata_id') ?>"
136139
placeholder="Q40218"
137-
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
140+
class="mt-1 block w-full rounded-md <?= $err('wikidata_id') ? 'border-red-500' : 'border-gray-300' ?> shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
141+
<?php if ($err('wikidata_id')): ?>
142+
<p class="mt-1 text-sm text-red-600"><?= $e($err('wikidata_id')) ?></p>
143+
<?php endif; ?>
138144
</div>
139145
<div>
140146
<label for="tgn_id" class="form-label"><?= __('Identificativo Getty TGN') ?></label>
141147
<input type="text" id="tgn_id" name="tgn_id" value="<?= $val('tgn_id') ?>"
142148
placeholder="7004329"
143-
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
149+
class="mt-1 block w-full rounded-md <?= $err('tgn_id') ? 'border-red-500' : 'border-gray-300' ?> shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
150+
<?php if ($err('tgn_id')): ?>
151+
<p class="mt-1 text-sm text-red-600"><?= $e($err('tgn_id')) ?></p>
152+
<?php endif; ?>
144153
</div>
145154
</div>
146155

0 commit comments

Comments
 (0)