feat(fr-bnf): French locale (fr_FR) + BNF SRU/UNIMARC integration#131
feat(fr-bnf): French locale (fr_FR) + BNF SRU/UNIMARC integration#131fabiodalez-dev wants to merge 31 commits into
Conversation
- Add fr_FR locale: locale/fr_FR.json (4080 keys), routes_fr_FR.json - Add installer/database/data_fr_FR.sql with French genres, CMS, emails - Backfill fr_FR in data_it_IT.sql, data_en_US.sql, data_de_DE.sql - Backfill fr_FR in migrate_0.7.4.sql (existing installs upgrade path) - Add French language option in installer step0.php (tricolor SVG) - Register fr_FR in Installer.php, installer/index.php, config/default_texts.php - SruClient.php: add parseMarcxchangeXml() for UNIMARC fields (200/214/700/701/702/010/073/215/461/676/101/330/600-608) - SruClient.php: register mxc namespace (info:lc/xmlns/marcxchange-v2) - SruClient.php: add quote_search_terms server flag for BNF CQL quoting - SruClient.php: add local-name() fallback for numberOfRecords XPath - plugins.php: add BNF preset (unimarcxchange, v1.2, quote_search_terms) - plugins.php: add version and quote_search_terms fields to Z39 server rows - book_form.php: add scraped_numero_serie and scraped_dimensions hidden inputs - 71 E2E tests: install-french.spec.js (48) + bnf-sru-features.spec.js (23)
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Code review fixes: - SruClient: catch \Throwable (not \Exception) in retry loop — TypeError from strict_types would bypass retry - SruClient: restore libxml_use_internal_errors state after XML parse; use \RuntimeException for parse errors - plugins.php: BNF preset URL changed from http:// to https:// - data_fr_FR.sql: hero button_link changed from /catalogue to /catalog (matches routes_fr_FR.json) - routes_fr_FR.json: add missing bibframe.book route (parity with it_IT/en_US) New test file install-compat-standard.spec.js (59 tests, 8 phases): - Phase 1: all seed files include all 4 supported languages - Phase 2: fr_FR route keys are compatible with it_IT (count, presence, no duplicates) - Phase 3: migrate_0.7.4.sql backfills fr_FR with correct column names - Phase 4: installer code registers fr_FR in all required locations - Phase 5: DB state after upgrade (fr_FR present, no duplicate defaults) - Phase 6: BNF preset uses HTTPS - Phase 7: SruClient \Throwable catch, libxml state, RuntimeException - Phase 8: data_fr_FR button links match routes_fr_FR.json (no Italian/German slugs)
- Restore `novalidate` on #bookForm (removed in 2c0264e), which caused browser HTML5 validation to intercept submit before the SweetAlert2 handler could fire — breaking book create/edit confirmation dialog - Fix smoke-install.spec.js search parameter: the /api/libri endpoint uses `search_text` not `search[value]` (DataTables format), so the wrong parameter silently returned the first 5 alphabetical books instead of the searched title, making createdBookId wrong and the edit-title verification fail
- fr_FR.json: +65 translated strings for LibraryThing plugin sections (Recensione e Valutazione, Visibilità nel Frontend, Condizione Fisica, etc.) - en_US.json, de_DE.json: add missing "Valore Corrente" translation - book_form.php: wrap LibraryThing field labels with __() so they respect the active locale (previously always rendered in Italian) - full-test.spec.js: Phase 21 (4 serial tests) verifies fr/en/de/it language switching including LibraryThing French strings assertion - Minor fixes: archives plugin.json trailing comma, CI workflow tweaks, phpstan baseline cleanup, test file updates
Upgrades from 0.7.4 → 0.7.5 now execute migrate_0.7.5.sql which inserts the fr_FR language row (INSERT IGNORE, idempotent). 0.7.4 installs that already have the row will see a no-op.
…activate fr_FR INSERT IGNORE left pre-existing fr_FR rows with is_active=0 untouched. ON DUPLICATE KEY UPDATE now forces is_active=1 on upgrade, so the language bootstrap (step 3: WHERE is_default=1 AND is_active=1) can find the French locale after the admin sets it as default.
setDefault() now sets is_active=1 alongside is_default=1, preventing the inconsistent state where a language is marked default but inactive. I18n::bootstrap() step 3 requires is_active=1, so an inactive default language was silently ignored and the app stayed in the previous locale. Also adds missing bibframe.book route key to routes_de_DE.json.
… dev schema detected Pre-release dev builds used source_code/source_id column names. The current migration expects source/authority_id. CREATE TABLE IF NOT EXISTS skipped the DDL on installations that had the dev table, causing ADD KEY idx_authority (source, authority_id) to fail with 'Key column source does not exist'. Add a guard that detects the old schema via source_code column presence and drops the empty dev table before recreating with the correct schema.
…sue #130) On Windows, RecursiveDirectoryIterator::getPathname() returns backslash paths, breaking str_replace prefix-stripping and mkdir target construction in copyDirectoryRecursive() and copyDirectory(). All paths now normalized to forward slashes at function entry; realpath() returns also normalized. Also remove unused SEED_FILES variable from install-compat-standard.spec.js (CodeQL js/unused-local-variable #20) and harden test selectors.
…ition archives-upload-assets.spec.js ensureUploadFixtures(): - Remove existsSync+writeFileSync TOCTOU pattern (race condition fix) - Replace hardcoded /tmp/ paths with path.join(os.tmpdir(), ...) for cross-platform safety and to satisfy CodeQL js/insecure-tmp rule
__() uses Italian as the native fallback — keys ARE Italian text, so if a key is missing from it_IT.json the function returns the key itself. it_IT.json is intentionally sparse (overrides only). Only en_US ↔ de_DE requires exact key-set parity.
CodeQL js/insecure-temporary-file:
- Add binary fixtures to tests/fixtures/ (jpg/pdf/mp3, csv/tsv) and
allow the directory in .gitignore (mirrors tests/seeds/ pattern)
- archives-upload-assets.spec.js reads from tests/fixtures/ instead of
writing predictable filenames to os.tmpdir()
OAI-PMH:
- Catch remaining \Throwable in record serialisation loop to skip
malformed metadata with a warning log instead of crashing
E2E test stability:
- bibframe-persistent-uri: check @id contains /id/work/{id}, not suffix /work
- book-form-comprehensive: simplified Choices.js check (no XPath)
- bulk-enrich: nullify ISBN before DELETE to avoid UK constraint on re-run
- multisource-scraping: accept 'Z39.50/SRU' as valid source for Nevermind
- smoke-install: make author input fill conditional (visible guard)
Fix groups (committed): - [FG-1] F002, F007 — storage/plugins/archives/ArchivesPlugin.php: verified - [FG-2] F003 — storage/plugins/bibframe-linked-data/BibframeLinkedDataPlugin.php: verified - [FG-3] F005 — storage/plugins/oai-pmh-server/OaiPmhServerPlugin.php: verified - [FG-4] F010 — app/Controllers/FrontendController.php: verified - [FG-5] F020 — storage/plugins/oai-pmh-server/views/book-digital-assets.php: verified - [FG-6] F023 — storage/plugins/archives/views/authorities/show.php: verified - [FG-7] F027 — app/Views/admin/plugins.php: verified - [FG-8] F028 — storage/plugins/archives/views/show.php: verified Post-fix review: 8/8 groups verified complete; 0 group(s) partial; 0 group(s) reverted.
Fix groups (committed): - [FG-1] F003 — storage/plugins/z39-server/classes/SRUServer.php: verified - [FG-2] F018 — storage/plugins/z39-server/classes/SruClient.php: verified - [FG-3] F019 — storage/plugins/openurl-resolver/OpenUrlResolverPlugin.php: verified - [FG-4] F020 — storage/plugins/resource-sync/ResourceSyncPlugin.php: verified - [FG-5] F023 — app/Controllers/ScrapeController.php: verified - [FG-6] F024, F025, F045 — storage/plugins/ncip-server/NcipServerPlugin.php: verified - [FG-7] F027 — storage/plugins/viaf-authority/ViafAuthorityPlugin.php: verified - [FG-8] F028 — storage/plugins/archives/ArchivesPlugin.php: verified - [FG-9] F029 — storage/plugins/oai-pmh-server/OaiPmhServerPlugin.php: verified Post-fix review: 9/9 groups verified complete; 0 group(s) partial; 0 group(s) reverted.
Fix groups (committed): - [FG-1] F007, F041, F012 — storage/plugins/oai-pmh-server/OaiPmhServerPlugin.php: verified - [FG-2] F031 — app/Views/frontend/layout.php, locale/en_US.json, locale/de_DE.json, locale/fr_FR.json, locale/it_IT.json: verified - [FG-3] F034 — app/Views/admin/plugins.php: verified Post-fix review: 3/3 groups verified complete; 0 group(s) partial; 0 group(s) reverted.
Reconciled fix (one merge pass after Phase 9.pre overlap): Findings: F008, F018, F029, F033, F040 Files: app/Controllers/FrontendController.php, app/Support/PluginManager.php, app/Support/Updater.php, app/Views/frontend/book-detail.php, app/Views/frontend/layout.php, storage/plugins/oai-pmh-server/OaiPmhServerPlugin.php Overlaps: app/Views/frontend/layout.php <- FG-1, FG-3 Findings addressed: - F008 (PluginManager::isActive cache): replace uncached `SELECT 1 FROM plugins` in FrontendController::bookDetail() and frontend/layout.php with per-process cached PluginManager::isActive() lookup. Eliminates an extra DB round-trip on every catalog page render (including anonymous crawls). - F018 (CWE-22 prefix-without-separator): tighten path-traversal guard in Updater.php — use str_starts_with($parentTarget, $realDest.'/') to prevent '/var/www/dest2' from passing when $realDest='/var/www/dest'. - F029 (WCAG 2.1 SC 1.1.1): add aria-hidden="true" to fa-archive icon in search-dropdown builder (frontend/layout.php JS). - F033 (WCAG 2.1 SC 1.1.1): add aria-hidden="true" to media-type icon in book-detail view (label is announced separately). - F040 (RFC 7235): split requireAdminForDownload() OAI-PMH auth flow — 401 + WWW-Authenticate when no credentials supplied, 403 when credentials present-but-invalid. Post-fix review: 1/1 group verified complete; 0 partial; 0 reverted.
PR #132 polish bundle: - book-detail.php: htmlspecialchars() around ucfirst($author['ruolo']) — was the only raw role output in the author span (F048). - tests/pr132-fix-regressions.spec.js: new E2E regression suite covering F003 (SRU errorResponse namespace), F018 (UNIMARC 461 $t/$v), F019 (OpenURL locale-aware book_url), F020 (ResourceSync 30-day deleted window), F024/F025 (NCIP UPDATE libri AND deleted_at IS NULL), F027 (ViafAuthority ensureSchema return shape), F029 (OAI-PMH ListMetadataFormats includes unimarc), F045 (NCIP admin endpoints reachable by staff), F048 (book-detail XSS escape). - tests/discogs-import.spec.js: mock Discogs scrape endpoint for deterministic E2E so the suite no longer depends on live Discogs API. - tests/series-collane-crud.spec.js: mysql invocation now supports TCP host alongside socket connection and pipes the migration via stdin. - tests/bulk-enrich.spec.js: raise per-test timeout to 240s and surface Gateway-Timeout retries as explicit errors. - locale/fr_FR.json: 849 additional Italian→French translations (admin UI, archives, NCIP, OAI-PMH, plugin metadata) — closes the FR locale gap left by feat/fr-bnf-integration. - storage/plugins/archives/views/index.php: responsive toolbar (hidden on mobile, accessible "more" dropdown), aria-hidden on decorative SVG icons.
15 fix-group Opus agents applied surgical fixes to 20 findings from
adamsreview rev_01KRDSE2PB0P2TGEY78GT38J4F. All edits PHPStan level 5
clean; compat with existing installs preserved per user directive.
Bug fixes:
- F003 ScrapeController::byIsbn(): conditional session_write_close
via request attribute marker (HTTP entry only; in-process callers
keep session lock so $_SESSION writes persist).
- F004 SearchController public search: cap core results at 12 before
Hooks::apply('search.unified.sources') so plugin sources (archives)
always get headroom in the 15-slot dropdown.
- F010 Updater::copyDirectory() (sibling of F018 hotspot): apply the
same prefix-collision guard (!== $realDest + str_starts_with
$realDest . '/').
- F027 ApiBookScraperPlugin merge: restore '|| === null' branch so
null-valued existing fields are overwritten by API data, matching
the "fill empty fields" inline comment.
- F067 OaiPmhServerPlugin bind_param: fix type string 'issssiii' ->
'isssiiii' for digital_assets insert ($bookId,$url,$filetype,$md5,
$filesize,$width,$height,$ppi).
- F072 OpenLibraryPlugin merge: same '|| === null' restoration as
F027 (via array_key_exists to keep PHPStan happy).
- F081 ViafAuthorityPlugin setAuthorIsniAction: preserve existing
VIAF authority assignment when setting/clearing ISNI; only stomp
authority_source when no prior VIAF exists.
- F083 Z39ServerPlugin fallback merge: same '|| === null' restoration.
- F084 Z39ServerPlugin decryptSetting: match PluginManager order
($_ENV first then getenv) so existing encrypted settings remain
decryptable under phpdotenv createImmutable (which populates $_ENV
but not getenv).
Archives plugin (5 findings, FG-10):
- F028 migrateImageColumns docblock updated to current behavior.
- F029 add fr_FR to public archive routes loop ([it_IT,en_US,de_DE,
fr_FR]).
- F031 handleAssetUpload data-loss: defer legacy document_path
unlink until AFTER the archival_unit_files INSERT succeeds.
- F032 remove htmlspecialchars from 10 Location HTTP headers
(HTTP, not HTML — entity encoding corrupts URLs with reserved chars).
- F034 archiveSearchPattern: use mb_strlen/mb_substr so UTF-8 input
(citta, editeur, Ubung) is not truncated mid-sequence.
i18n / install:
- F020 data_it_IT.sql + data_en_US.sql: add ON DUPLICATE KEY UPDATE
to languages INSERT (mirrors de_DE/fr_FR pattern; idempotent
re-install over existing DB).
- F023 data_*.sql key counts realigned to actual JSON file contents
(it_IT=493, en_US=4653, de_DE=4653, fr_FR=4993; replaces stale
2015/4080/4145 values that disagreed across migrations and JSON).
- F025 routes_fr_FR.json: translate all 33 routes to French
(/connexion, /catalogue, /livre, /auteur, /editeur, ...) mirroring
de_DE convention with accent-free URLs for SEO.
Plugin registry repair:
- F021 Create migrate_0.7.6.sql to register oai-pmh-server +
viaf-authority (omitted from migrate_0.7.4.sql despite being in
BundledPlugins::LIST). Bumps version.json to 0.7.6 so the migration
fires. Historic migrate_0.7.4.sql untouched per compat directive.
Linked data:
- F045 BibframeLinkedDataPlugin: previously-dead $viafId now emits
Work-level owl:sameAs VIAF URI, mirroring the per-author pattern.
Release tooling:
- F026 scripts/create-release.sh: restore executable bit
(mode 100644 -> 100755) via git update-index --chmod=+x.
Compat-preservation strategy applied per user directive:
- Historic migration files (migrate_0.7.4.sql, migrate_0.7.5.sql)
NOT edited; new migrate_0.7.6.sql handles missing plugin registry.
- Seeder files updated only for fresh-install correctness; existing
installs at 0.7.5 unaffected.
- ScrapeController fix uses request-attribute opt-in so HTTP behavior
is unchanged.
Post-fix review: 20/20 verified by fix-group PHPStan checks; no
regression triggered; 0 reverted.
Review artifact: rev_01KRDSE2PB0P2TGEY78GT38J4F
17 fix-group Opus agents applied surgical fixes to 30 findings from
adamsreview rev_01KRDSQ3MA7PCZDZJHCY9ZB7JF (medium-confidence batch).
All edits PHPStan level 5 clean; backward-compat preserved per user
directive — historic migrations untouched, opt-in toggles, default
behavior preserved on existing installs.
== Security (8 fixes) ==
- F007 HtmlHelper getBaseUrl: optional APP_TRUSTED_HOSTS whitelist
(env var, comma-separated). When set, validates HTTP_HOST and
X-Forwarded-Host (proxy-gated). When unset, behavior unchanged.
- F030 Archives: already covered by prior F032 (no-op verified).
- F033 ArchivesPlugin findLeafTitle: AND deleted_at IS NULL guard.
- F048 NcipServerPlugin: MAX_REQUEST_BYTES dropped 1 MiB to 256 KiB
(unauth /ncip endpoint; real NCIP messages <10 KB); unset($body) +
unset($xml) to free SimpleXML DOM early.
- F058 OAI-PMH: honest deletedRecord advertising — Identify now
returns 'no' (instead of false 'persistent') when trigger install
failed (shared hosting without TRIGGER privilege).
- F059 OAI baseURL: oaiBaseUrl() helper logs one-shot warning when
APP_CANONICAL_URL unset (OAI ids are Host-header-spoofable).
- F077 ResourceSync: opt-in `require_basic_auth` plugin setting
(default '0' = spec-compliant public). When '1', endpoints require
admin/staff Basic Auth (RFC 7235 401+WWW-Authenticate / 403).
- F078 ResourceSync /changelist?from=: tombstones capped at 90-day
window (was unbounded); live rows now include deleted_at IS NULL.
- F079 ViafAuthority curl: add CURLOPT_REDIR_PROTOCOLS to prevent
redirect-to-file:// SSRF on older libcurl.
- F082 ViafAuthority validateCsrf: remove Basic Auth bypass (BREAKING
for API clients — they must now supply csrf_token in body or
X-CSRF-Token header for write endpoints).
== Correctness (17 fixes) ==
- F006 HtmlHelper isRemoteAddrTrustedProxy: IPv6 CIDR support added
(was silently skipping IPv6 entries).
- F008 QueryCache stampede: at flock timeout now performs 5 LOCK_NB
retries before unprotected fallthrough; warns on bypass.
- F012 book-detail.php: skip empty sameAs to avoid noisy
"sameAs": [] in JSON-LD.
- F015 catalog.php: archive URL regex broadened to allow query
strings, fragments, percent-encoded chars while still rejecting
javascript:/data: and CRLF injection.
- F018 bin/pinakes viaf-reconcile-all: keyset-paginated chunks of
500 rows (was loading entire SELECT into memory; OOM on 100k+
catalogs).
- F022 Append to migrate_0.7.6.sql: ON DUPLICATE KEY UPDATE forces
fr_FR is_active=1 for installs that only ran migrate_0.7.4.sql
(which used INSERT IGNORE). Historic 0.7.4 untouched.
- F042 archives/show.php Schema.org: image and associatedMedia
.contentUrl now wrap via absoluteUrl() (matches isPartOf.url
pattern; Schema.org consumers require absolute URIs).
- F044 Bibframe wantsMachineReadable: removed application/json from
accept matcher (was returning Content-Type application/ld+json for
Accept: application/json — strict negotiators reject).
- F047 NcipServerPlugin: hardcoded url('/admin/login') replaced with
url(RouteTranslator::route('login')) so redirect honors locale-
translated login path (/accedi IT, /login EN, /anmelden DE).
- F052 NcipServerPlugin closeLoan: replaced brittle affected_rows!==1
rollback with idempotent retry: SELECT current stato when
affected_rows=0; treat 'restituito' as no-op (NCIP partners can
safely retry duplicate CheckIn).
- F057 OAI-PMH onUninstall: DROP TRIGGER IF EXISTS for both triggers
(was empty {}). Tables kept for data preservation.
- F060 OAI-PMH oaiListSets archives: requires BOTH plugin active AND
table exists (was table-only); set correctly disappears when
Archives is deactivated.
- F063 OAI-PMH resumption tokens: expires_at via MySQL DATE_ADD(
NOW(), INTERVAL) instead of PHP date() — fixes TZ mismatch causing
spurious badResumptionToken (shared hosting Europe/Rome vs UTC).
- F065 OAI-PMH bookToIso2709 leader: reconciled UNIMARC leader
between MARCXML and binary serializations ('nam a22...' + ' u 4500',
IFLA 2008 compliant).
- F068 oai book-digital-assets.php: wrap oaiFormatBytes() in
function_exists() guard (prevents fatal on multi-include via hook).
- F080 ViafAuthority reconcileSearch: switch LIKE ESCAPE clause from
backslash to '!' — unambiguous across all sql_mode settings (was
fragile under NO_BACKSLASH_ESCAPES).
- F086 SRUServer: diagnostic <uri>/<details>/<message> now in NS_DIAG
namespace (info:srw/diagnostic/1/) per SRU spec.
- F088 SruClient rate limiter: 3-tier (APCu preferred, file-lock
fallback, in-process degraded) — was per-PHP-process only (multi-
worker box got N× expected rate to BNF/SUDOC).
- F091 SruClient parseMarcxchangeXml: fallback xpath now descends
into recordData first (was matching outer SRU envelope).
- F092 UNIMARCXMLFormatter: switch to MARCXchange namespace (info:lc
/xmlns/marcxchange-v2). BREAKING for harvesters dispatching on
MARC21 namespace — UNIMARC field codes 200/210/700 belong in
MARCXchange, not MARC21 slim. SruClient already routes UNIMARC
through parseMarcxchangeXml so client-side was already correct.
Compat-preservation strategy applied per user directive:
- Historic migrations (migrate_0.7.4.sql, migrate_0.7.5.sql) NOT
edited; F022 extends migrate_0.7.6.sql.
- HtmlHelper Host-trust gated behind APP_TRUSTED_HOSTS env (unset =
pre-fix behavior preserved).
- ResourceSync Basic Auth opt-in (default off = spec-compliant).
- OAI deletedRecord soft-fallback (advertise 'no' instead of failing
activation when triggers can't be installed).
- ViafAuthority Basic Auth CSRF removal documented as breaking.
- UNIMARCXMLFormatter namespace change documented as breaking.
Post-fix review: 30/30 verified by fix-group PHPStan checks; 0
regression; 0 reverted.
Review artifact: rev_01KRDSQ3MA7PCZDZJHCY9ZB7JF
…irmation)
20 finding promossi via /adamsreview:walkthrough batch (1 Opus briefer per
tutti, 16 fix-group Opus paralleli). Tutti option A (compat-preserving).
== Architecture (2) ==
- F001 FrontendController catalogAPI(): rimuovi dead $archiveResults lookup
+ Hooks::apply ('frontend.catalog.archive_results') + 'archives' chiave
dalla JSON response. catalog() (full-page) preserva il pannello archives
invariato. Nessun JS consumer leggeva response.archives.
- F019 Installer.php installPluginsFromZip(): aggiunto comment block
documentando perche solo 5 plugin storici sono auto-attivati al fresh
install; gli altri 11 (BundledPlugins::LIST) sono registrati piu tardi
da autoRegisterBundledPlugins() e restano deactivated. Allow-list non
espansa per conservare opt-in policy.
== Policy / Doc drift (2) ==
- F046 NcipServerPlugin docblock: aggiunti RequestItem e CancelRequestItem
alla lista 'Supported messages' (match() handler gia li gestiva; capability
XML gia li advertise).
- F076 openurl-resolver/wrapper.php: corretto comment classname.
PluginManager::getPluginClassName('openurl-resolver') restituisce
'OpenurlResolverPlugin' (single capital, per explode('-')+ucfirst), non
'OpenUrlResolverPlugin'. Classe non rinominata (PHP case-insensitive).
== UX accessibility (3) ==
- F017 user_layout.php: aria-hidden=true sull'icona fa-archive nel search
dropdown (line 1216) + $searchViewAllLabel via json_encode(__('Vedi tutti
i risultati')) per i18n. Mirror del fix gia applicato a frontend/layout.
- F040 archives/views/public/index.php: clear-filters button con aria-label
+ 44x44 min touch target + fa-times icon visibile.
- F069 oai-pmh-server/views/book-digital-assets.php: aria-label sui delete
icon button (static + JS-appended).
== UX i18n & plurals (3) ==
- F024 en_US.json + de_DE.json: aggiunte 8 key mancanti (archive form
section headings + plugin uninstall aria-label).
- F039 archives/views/index.php: __n('%d risultato', '%d risultati', N)
per pluralizzazione corretta + $levelLabel mapping (fonds/series/file
/item → Fondo/Serie/Fascicolo/Unita) invece del raw enum.
- F041 archives/views/public/index.php: stessa pluralizzazione + branch
espliciti 'dal'/'fino al' al posto dell'ellipsis '...' nei date range.
== UX behavior (9) ==
- F005 Language::setDefault(): docblock espanso per documentare la side
effect (inactive→active). Controller (LanguagesController::setDefault)
ora rileva is_active=0 prima del setDefault e emette flash_info via
__('Language %s has been activated automatically'). Aggiunto blocco
flash_info nel template languages/index.
- F014 catalog.php: archive panel suggestion ('Trovato anche
nell'archivio') ora rendered solo quando empty($books); prima si
mostrava sempre con archiveResults non-empty.
- F035 archives/views/authorities: list view salva
window.location.href su sessionStorage; show.php usa quello come
fallback intelligente quando document.referrer e empty (apertura
in nuova tab da search). Pattern try/catch per private-mode/disabled
storage.
- F037 archives/views/form.php: placeholder version_note semplificato
(rimosso ternary ridondante) + esempio "inventario 2024"→"inventario
2026". Tradotto in tutte le 4 locale.
- F038 archives/views/index.php: inline <style> block estratto in
storage/plugins/archives/assets/css/archives-admin.css (rispetta la
Slim asset route convention; #arc-actions-details[open] .arc-chevron
+ summary::-webkit-details-marker).
- F043 archives/views/public/show.php: filename fallback sanitizzato
via preg_replace per evitare path leak; file size renderizzato accanto
al MIME via inline closure bytesStr (legge $uf['file_size'] o fallback
a @filesize() guarded).
- F054 ncip-server/views/partners.php: native confirm() sostituito con
Swal.fire mostrando nome/ISIL/endpoint del partner + warning su
transazioni orfane. Fallback a window.confirm se Swal non disponibile.
i18n via PHP-rendered json_encode().
- F055 ncip-server/views/transactions.php: $statusLabel map per
localizzare le badge ok/error/pending (era raw protocol string in
tutte le locale).
- F070 oai-pmh-server/views/book-digital-assets.php: client-side
validation con inline errors + aria-invalid prima del fetch (URL
via URL(), MD5 regex /^[0-9a-fA-F]{32}$/, filesize/width/height/ppi
positive int). setFieldError() + clearFieldErrors() helpers.
- F071 oai-pmh-server/views/book-digital-assets.php: native confirm()/
alert() sostituiti con Swal.fire (toast success + error dialogs);
appendRow() distingue URL accettato vs respinto (rejected: aria-label
warning, no target=_blank, classe text-red-500).
Compat-preservation preserved per direttiva utente:
- Nessun edit a migration storiche (gia da prima)
- SQL behavior in Language::setDefault() invariato; docblock + flash
message sono additive
- Tutti i fix opt-in o additivi (icon labels, plurals, validation,
alert UX); nessun breaking change
Post-fix review: 20/20 verified via PHPStan/php -l/json_lint dei fix-group.
Total ~1.1M tokens (16 Opus fix-group agents in parallelo, 1 batch briefer).
Review artifact: rev_01KRDSQ3MA7PCZDZJHCY9ZB7JF
F075: localBookUrl() built origin from $request->getUri()->getHost(), bypassing the APP_TRUSTED_HOSTS guard added to HtmlHelper::getBaseUrl(). A spoofed Host header could produce a 302 redirect to an attacker- controlled domain. Use absoluteUrl() which routes through the trusted- host validation. F089/F090: quote_search_terms wrapped $term in CQL double-quotes without escaping embedded " or \. A search term containing " would produce malformed CQL sent to external SRU servers (BNF, SUDOC). Backslash- escape both chars per the CQL specification.
|
Duplicato di #132 (PR completa per v0.7.5 → main). Chiuso. |
Summary
locale/fr_FR.json(4080 chiavi),locale/routes_fr_FR.json(slug inglesi),installer/database/data_fr_FR.sql(generi, CMS, email in francese), radio tricolore nell'installer step0migrate_0.7.4.sql(INSERT IGNORE) per installazioni esistentiSruClient.php— metodoparseMarcxchangeXml()per campi UNIMARC (200 titolo, 214 editore/anno, 700/701/702 autori, 010 ISBN, 073 EAN, 215 pagine, 461 collana, 676 Dewey, 101 lingua, 330 abstract, 600-608 soggetti); namespace MARCXchangeinfo:lc/xmlns/marcxchange-v2; flagquote_search_termsper quoting CQL BNF; fallbacklocal-name()sunumberOfRecordsunimarcxchange, v1.2, quote_search_terms=true), campo version, checkbox quote, dropdown syntax con opzioneunimarcxchangescraped_numero_serieescraped_dimensionsinstall-french.spec.js(48 test in 6 fasi) +bnf-sru-features.spec.js(23 test in 4 fasi); PHPStan level 5: OKTest Plan
install-french.spec.js— 44 passati, 4 saltati (installer skip corretto), 0 fallitibnf-sru-features.spec.js— 23/23 passati2070360024(Le Grand Meaulnes) restituisce record UNIMARCmigrate_0.7.4.sqlinserisce fr_FR nella tabella languages