feat: German locale, dynamic route registration, GitHub API token, manual upgrade script#69
feat: German locale, dynamic route registration, GitHub API token, manual upgrade script#69fabiodalez-dev wants to merge 39 commits intomainfrom
Conversation
- Updater: add GitHub PAT support to bypass 60 req/hr rate limit (5000 with token) - Updater: centralize headers via getGitHubHeaders(), store token in system_settings - UpdateController: add saveToken() endpoint with CSRF validation - Admin UI: GitHub token card with save/remove, masked display, setup instructions - Route: POST /admin/updates/token - scripts/manual-upgrade.php: standalone upgrade for users stuck on old versions - Password-protected, auto-detects root path - DB backup via mysqldump, ZIP upload + extraction - Preserves .env, uploads, plugins, covers, configs - Runs pending migrations (idempotent), clears cache
New languages added via Admin → Languages now automatically get their translated routes registered without code changes. Closes discussion #65.
- Add locale/de_DE.json with 4,009 translated strings - Add locale/routes_de_DE.json with German URL routes - Add installer/database/data_de_DE.sql with German seed data (genres, email templates, CMS pages, cookie banner, home content) - Add Deutsch option to installer language selection (step 0) - Add de_DE to I18n fallback locales and Installer locale map - Make installer __() translation function language-agnostic
|
@CodeRabbit full review |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds German locale and extensive German seed data; implements encrypted GitHub API token storage with admin UI and API; introduces a standalone manual-upgrade script; enhances genre descendant filtering, book metadata, DataTables i18n centralization, date-format fallbacks, UI shortcuts, many vendor updates, and assorted frontend/backend fixes. Changes
Sequence Diagram(s)sequenceDiagram
rect rgba(0,128,0,0.5)
participant Browser
end
rect rgba(0,0,255,0.5)
participant Server
note right of Server: UpdateController::saveToken
end
rect rgba(128,0,128,0.5)
participant Updater
end
rect rgba(255,165,0,0.5)
participant SettingsStore
end
Browser->>Server: POST /admin/updates/token (csrf, github_token)
Server->>Server: validate CSRF & AdminAuth
Server->>Updater: saveGitHubToken(token)
Updater->>SettingsStore: encrypt & persist token in system_settings
SettingsStore-->>Updater: ack
Updater-->>Server: success / throw on error (logged via SecureLogger)
Server-->>Browser: JSON {ok:true,msg:"..."} or 500 error
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (3)
scripts/manual-upgrade.php (2)
52-81: Helper functions duplicated fromapp/Support/Updater.php.
formatBytes,deleteDirectory, and the env-loading pattern are near-identical copies of methods inUpdater.php(see relevant code snippets). This is expected for a standalone script that can't import app classes, so just noting it for awareness — keep them in sync manually if either side changes.Also applies to: 83-102, 109-118, 120-141
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/manual-upgrade.php` around lines 52 - 81, The functions loadEnv, formatBytes, and deleteDirectory are duplicated from app/Support/Updater.php; add a short inline comment above each (loadEnv, formatBytes, deleteDirectory) stating that this duplication is intentional for a standalone script and include a pointer to the canonical implementation in Updater.php so future maintainers know to keep them in sync; do not change behavior—just document the duplication clearly so it’s obvious why these near-identical helpers exist and where to update them.
269-288: DB password visible in process listing duringmysqldump.The
--password=argument is visible to any user who can runps auxon the server. The standard mitigation is a temporary--defaults-extra-file:$tmpCnf = tempnam(sys_get_temp_dir(), 'my'); file_put_contents($tmpCnf, "[client]\npassword=" . $env['DB_PASS'] . "\n"); chmod($tmpCnf, 0600); // use --defaults-extra-file=$tmpCnf instead of --password= // unlink($tmpCnf) after execGiven this is a temporary script, the risk is lower, but worth noting.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/manual-upgrade.php` around lines 269 - 288, Replace the plain --password= arg (used when building $args for $mysqldumpBin) with a secure temporary defaults file: create a temp file (e.g. $tmpCnf), write a [client] section containing the password from $env['DB_PASS'], chmod it to 0600, add '--defaults-extra-file=' . $tmpCnf to $args instead of '--password=', run exec($safeCmd, ... ) as before, then immediately unlink($tmpCnf) after exec to remove the file; keep references to $mysqldumpBin, $args, $env['DB_PASS'], $safeCmd, $backupFile and exec() so you replace the insecure password exposure without changing the overall flow.app/Controllers/UpdateController.php (1)
34-38:$datavariable is assigned but never used (static analysis).The
compact()result is stored in$databut the view file (included viarequireon line 39) accesses the individual variables ($githubTokenMasked,$hasGithubToken, etc.) directly from the local scope rather than through$data. This is a pre-existing pattern, but the static analysis flagging is valid.♻️ Option: remove the unused assignment
- $data = compact('updateInfo', 'requirements', 'history', 'changelog', 'githubTokenMasked', 'hasGithubToken'); + // Variables are accessed directly by the view via local scope🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Controllers/UpdateController.php` around lines 34 - 38, The assignment to $data via $data = compact('updateInfo', 'requirements', 'history', 'changelog', 'githubTokenMasked', 'hasGithubToken') is unused and triggers static analysis; remove that $data assignment line (leaving ob_start() and the individual local variables like $githubTokenMasked, $hasGithubToken, $updateInfo, $requirements, $history, $changelog intact) so the view included afterward consumes the existing locals directly, or alternatively replace the require-based view consumption by using the $data array consistently—pick one approach and remove the unused compact() call if you keep the current require/local-variable pattern.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/Support/Updater.php`:
- Around line 193-196: The token load/save paths (loadGitHubToken and
saveGitHubToken) call $stmt->execute(), $stmt->get_result() and
$result->fetch_column() without checking for errors; update both functions to
validate $stmt->execute() return (or check $stmt->error), ensure $result !==
false before calling fetch_column(), and verify fetch_column() actually returned
a value; for saveGitHubToken also check execution success and affected_rows to
confirm persistence and return/throw a failure indicator instead of reporting
success when execute() failed; use the existing $stmt variable and these method
names to locate and add proper error handling and early returns.
- Around line 186-200: The GitHub token is being stored and read in plaintext;
add two helpers (encryptUpdaterSecret(string): string and
decryptUpdaterSecret(string): string) on the Updater class and use them when
persisting and loading $this->githubToken: when saving the token (the code
around where $this->githubToken is written to system_settings, referenced in the
later block ~224-241) call encryptUpdaterSecret() and store the cipher text, and
when loading (the code shown here that prepares the SELECT and assigns
$this->githubToken) call decryptUpdaterSecret($value) and only set
$this->githubToken if decryption succeeds and returns a non-empty string; also
handle null/empty DB values and catch/deal with decryption errors so corrupt
ciphertext does not crash the updater.
In `@installer/classes/Installer.php`:
- Around line 263-265: The installer adds German mappings ('de', 'de_de',
'de_DE') in the locale map but then forces non-whitelisted locales to 'it_IT';
update createEnvFile() to include 'de_DE' in its allowed locales list (in the
validation/whitelist that currently contains ['it_IT','en_US']) so APP_LOCALE
can be set to 'de_DE', and update getInstallerLocale() to handle the German case
by adding a branch that returns 'de_DE' for the German mapping keys (so the
installer will load German texts); reference createEnvFile() and
getInstallerLocale() and ensure the whitelist and the new case use the exact
'de_DE' identifier.
In `@installer/database/data_de_DE.sql`:
- Around line 7-340: The seed file uses many plain INSERTs with explicit IDs
(tables: generi, system_settings, cms_pages, email_templates, languages,
home_content, themes) which cause duplicate-key failures on re-run and can leave
FOREIGN_KEY_CHECKS disabled; convert these into idempotent/upsert operations
(e.g. replace plain INSERTs with INSERT ... ON DUPLICATE KEY UPDATE or REPLACE
INTO) for each affected table and consolidate repeated system_settings inserts
into a single guarded block (use VALUES(...) ON DUPLICATE KEY UPDATE) and ensure
the script always re-enables FOREIGN_KEY_CHECKS (wrap inserts in a transaction
or add a finally-style restore) so partial failures won’t leave the session with
foreign key checks off.
In `@README.md`:
- Around line 28-45: Update the v0.4.9.2 release notes in README.md to include
the missing major features: add entries for German locale support, dynamic
locale route registration, GitHub updater token management, and the manual
upgrade script under the "Genre Management, Book Filters & Bug Fixes" section
(or a new "Locale & Upgrade Improvements" subsection) so admins see these
shipped features alongside the existing items; ensure the wording matches the
style of the other bullets and includes concise descriptions for "German locale
support", "dynamic locale route registration", "GitHub updater token
management", and "manual upgrade script".
In `@scripts/manual-upgrade.php`:
- Around line 424-451: The current use of $db->multi_query can lose errors from
earlier statements and cause "Commands out of sync"; replace multi_query usage
in the migration runner with splitting the migration SQL (from the file
processed by the migration loop) into individual statements (e.g., explode on
';' but trim and ignore empty/comment-only parts), then execute each statement
with $db->query() and check $db->errno/$db->error after each execution; if any
statement fails (and errno not in the existing $ignorableErrors list), log the
specific statement error and abort recording the migration (do not run the
INSERT IGNORE via $recStmt or push to $migrationsRun), otherwise continue
through all statements and only then run the existing $recStmt
prepare/execute/close and push $migVersion and OK log; this removes multi_query
and prevents "Commands out of sync" for subsequent prepare()/query() calls.
- Around line 29-30: Change the PHP runtime settings so errors are not rendered
to the browser: replace the ini_set call that sets display_errors to '1' with
display_errors = '0' (i.e., change ini_set('display_errors', '1') to
ini_set('display_errors', '0')), and ensure error logging remains enabled (e.g.,
keep error_reporting(E_ALL) and enable log_errors via ini_set('log_errors','1')
or a configured error_log) so diagnostics go to logs instead of exposing
sensitive data.
- Line 23: The script defines a hardcoded default secret via the
UPGRADE_PASSWORD constant which is a security risk; update the auth flow to
refuse to run when UPGRADE_PASSWORD equals the known default "pinakes2026" by
checking the UPGRADE_PASSWORD constant at startup (or at the auth check) and
aborting with a clear error instructing the operator to set a new password (or
implement a first-use setter) before continuing; reference the UPGRADE_PASSWORD
constant and the existing auth check block to add this validation and exit early
if the sentinel/default value is detected.
---
Nitpick comments:
In `@app/Controllers/UpdateController.php`:
- Around line 34-38: The assignment to $data via $data = compact('updateInfo',
'requirements', 'history', 'changelog', 'githubTokenMasked', 'hasGithubToken')
is unused and triggers static analysis; remove that $data assignment line
(leaving ob_start() and the individual local variables like $githubTokenMasked,
$hasGithubToken, $updateInfo, $requirements, $history, $changelog intact) so the
view included afterward consumes the existing locals directly, or alternatively
replace the require-based view consumption by using the $data array
consistently—pick one approach and remove the unused compact() call if you keep
the current require/local-variable pattern.
In `@scripts/manual-upgrade.php`:
- Around line 52-81: The functions loadEnv, formatBytes, and deleteDirectory are
duplicated from app/Support/Updater.php; add a short inline comment above each
(loadEnv, formatBytes, deleteDirectory) stating that this duplication is
intentional for a standalone script and include a pointer to the canonical
implementation in Updater.php so future maintainers know to keep them in sync;
do not change behavior—just document the duplication clearly so it’s obvious why
these near-identical helpers exist and where to update them.
- Around line 269-288: Replace the plain --password= arg (used when building
$args for $mysqldumpBin) with a secure temporary defaults file: create a temp
file (e.g. $tmpCnf), write a [client] section containing the password from
$env['DB_PASS'], chmod it to 0600, add '--defaults-extra-file=' . $tmpCnf to
$args instead of '--password=', run exec($safeCmd, ... ) as before, then
immediately unlink($tmpCnf) after exec to remove the file; keep references to
$mysqldumpBin, $args, $env['DB_PASS'], $safeCmd, $backupFile and exec() so you
replace the insecure password exposure without changing the overall flow.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
README.mdapp/Controllers/UpdateController.phpapp/Routes/web.phpapp/Support/I18n.phpapp/Support/Updater.phpapp/Views/admin/updates.phpinstaller/classes/Installer.phpinstaller/database/data_de_DE.sqlinstaller/index.phpinstaller/steps/step0.phplocale/de_DE.jsonlocale/routes_de_DE.jsonscripts/manual-upgrade.php
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
README.md (4)
8-8:⚠️ Potential issue | 🟡 Minor"Languages" header is stale — German is no longer listed.
This PR adds a full
de_DElocale. Line 8 still readsLanguages: Italian, English; it should be updated to include German/Deutsch.📝 Proposed fix
-> **Open-Source Integrated Library System** -> License: GPL-3 | Languages: Italian, English +> **Open-Source Integrated Library System** +> License: GPL-3 | Languages: Italian, English, Deutsch🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` at line 8, Update the stale "Languages" header string that currently reads "License: GPL-3 | Languages: Italian, English" to include German (Deutsch) — e.g., change the header to "License: GPL-3 | Languages: Italian, English, German (Deutsch)" or similar; ensure the displayed languages match the added de_DE locale so the README's languages list and the new locale are consistent.
219-219:⚠️ Potential issue | 🟡 MinorQuick Start step 4 still limits language choice to Italian or English.
With the German locale now available, the installer step should be reflected here too.
📝 Proposed fix
-4. **Select language** (Italian or English). +4. **Select language** (Italian, English, or Deutsch).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` at line 219, Update the Quick Start step text so the installer language choice includes German; replace the existing line "Select language (Italian or English)." in the README's Quick Start section with a version that lists Italian, English, and German (e.g., "Select language (Italian, English or German).") so the documentation matches the newly added German locale.
344-344:⚠️ Potential issue | 🟡 MinorOPAC description still advertises only two languages.
Line 344 reads
Responsive, multilingual frontend (Italian + English)— German is now a supported frontend locale and should be listed here.📝 Proposed fix
-- **Responsive, multilingual frontend** (Italian + English) +- **Responsive, multilingual frontend** (Italian, English, Deutsch)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` at line 344, Update the OPAC feature line that currently reads "Responsive, multilingual frontend (Italian + English)" to include German; locate the exact string "Responsive, multilingual frontend (Italian + English)" and change it to something like "Responsive, multilingual frontend (Italian, English, German)" so the README accurately lists supported frontend locales.
362-370:⚠️ Potential issue | 🟡 MinorAuto-Updater section omits the new GitHub API token capability.
The feature list describes the updater but has no mention of the optional personal access token (stored in
system_settings, manageable from Admin → Updates) introduced in this PR to raise GitHub API rate limits. Worth adding a bullet so admins who hit rate-limit errors know the option exists.📝 Suggested addition
- **Security** — CSRF validation, admin-only access, path traversal protection, Zip Slip prevention +- **GitHub API token** — Optional personal access token (Admin → Updates) to raise GitHub API rate limits🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` around lines 362 - 370, Add a bullet to the Auto-Updater feature list mentioning the new optional GitHub personal access token support: note that admins can store a GitHub API token in system_settings (configurable from Admin → Updates) to raise GitHub API rate limits and avoid rate-limit errors when using the built-in update system. Ensure the text matches the style of the other bullets and briefly states where to configure the token and its purpose.
♻️ Duplicate comments (8)
README.md (1)
28-45:⚠️ Potential issue | 🟡 Minorv0.4.9.2 changelog still omits major features shipped in this PR.
The new section covers genre management and bug fixes but does not mention German locale, dynamic locale route registration, GitHub API token for the updater, or the manual upgrade script.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` around lines 28 - 45, The v0.4.9.2 changelog entry in README.md is missing several items; update the release notes under the "🏷️ Genre Management, Book Filters & Bug Fixes" (or the v0.4.9.2 section) to include entries for the German locale addition, dynamic locale route registration, the GitHub API token support for the updater, and the manual upgrade script; add concise bullet points matching the existing style and wording so the changelog reflects all features shipped in this PR.installer/database/data_de_DE.sql (1)
7-342:⚠️ Potential issue | 🟠 MajorSeed script is not safely rerunnable.
From Line 7 onward, most statements are plain
INSERTwith explicit IDs/unique keys. On rerun, duplicate-key failures can halt execution before all records are applied, making install retries fragile.♻️ Suggested direction
-INSERT INTO `system_settings` (`category`, `setting_key`, `setting_value`, `description`) VALUES -('cms', 'events_page_enabled', '1', '...'); +INSERT INTO `system_settings` (`category`, `setting_key`, `setting_value`, `description`, `updated_at`) VALUES +('cms', 'events_page_enabled', '1', '...', NOW()) +ON DUPLICATE KEY UPDATE + setting_value = VALUES(setting_value), + description = VALUES(description), + updated_at = NOW();Apply the same idempotent strategy (
ON DUPLICATE KEY UPDATE/INSERT IGNOREwhere appropriate) to the other seeded tables with explicit IDs.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@installer/database/data_de_DE.sql` around lines 7 - 342, The seed file uses plain INSERTs with explicit IDs (e.g. INSERT INTO `generi` VALUES (...), INSERT INTO `cms_pages`, INSERT INTO `email_templates`, INSERT INTO `languages`, INSERT INTO `home_content`, INSERT INTO `themes`) which will fail on reruns due to duplicate keys; make these statements idempotent by converting them to either INSERT ... ON DUPLICATE KEY UPDATE (updating relevant columns like setting_value/description/updated_at or content fields) or INSERT IGNORE where overwriting isn't desired, and apply the same pattern to the bulk generi inserts and other explicit-id inserts so the script can be safely re-run without error.app/Support/Updater.php (2)
186-200:⚠️ Potential issue | 🟠 MajorGitHub token is stored in plaintext.
Line 199 and Line 240 keep the raw credential in DB/memory persistence flow. This increases blast radius in dumps/backups and violates secret-handling best practices.
🔐 Proposed fix
--- a/app/Support/Updater.php +++ b/app/Support/Updater.php @@ - if (is_string($value) && $value !== '') { - $this->githubToken = $value; + if (is_string($value) && trim($value) !== '') { + $plain = $this->decryptUpdaterSecret($value); + if ($plain !== '') { + $this->githubToken = $plain; + } } @@ - $stmt->bind_param('sss', $cat, $key, $token); + $encrypted = $token === '' ? '' : $this->encryptUpdaterSecret($token); + $stmt->bind_param('sss', $cat, $key, $encrypted); $stmt->execute(); $stmt->close(); $this->githubToken = $token;private function encryptUpdaterSecret(string $plain): string { /* use app key */ } private function decryptUpdaterSecret(string $cipher): string { /* use app key */ }Also applies to: 224-241
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Support/Updater.php` around lines 186 - 200, The code reads and stores the GitHub token in plaintext; add symmetric encryption/decryption around persistence: implement private methods encryptUpdaterSecret(string $plain): string and decryptUpdaterSecret(string $cipher): string that use the application key (from config/env) and a secure crypto library, then update the Updater DB write path to call encryptUpdaterSecret before binding/storing the setting and update the read path (the block using $stmt->bind_param / $stmt->execute / $result->fetch_column and the later similar block around lines 224-241) to retrieve the stored value and call decryptUpdaterSecret before assigning $this->githubToken; ensure existing code handles decryption failures gracefully and keep DB column as ciphertext.
193-196:⚠️ Potential issue | 🟠 MajorHandle statement execution failures in token load/save paths.
Line 193 through Line 196 and Line 236 through Line 238 assume
execute()/get_result()succeed. Failures can silently desync state (save) or break loading logic.✅ Proposed fix
@@ - $stmt->execute(); - $result = $stmt->get_result(); - $value = $result->fetch_column(); + if (!$stmt->execute()) { + $stmt->close(); + return; + } + $result = $stmt->get_result(); + if ($result === false) { + $stmt->close(); + return; + } + $value = $result->fetch_column(); @@ - $stmt->bind_param('sss', $cat, $key, $token); - $stmt->execute(); + $stmt->bind_param('sss', $cat, $key, $token); + if (!$stmt->execute()) { + $error = $this->db->error; + $stmt->close(); + throw new Exception(__('Errore nel salvataggio del token') . ': ' . $error); + } $stmt->close();#!/bin/bash set -euo pipefail echo "== loadGitHubToken/saveGitHubToken blocks ==" sed -n '181,245p' app/Support/Updater.php echo echo "== Execute/get_result calls in Updater ==" rg -n -C2 '\-\>execute\(|\-\>get_result\(|fetch_column\(' app/Support/Updater.phpExpected result: confirm whether error paths are checked before consuming
get_result()and before reporting save success.Also applies to: 236-238
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Support/Updater.php` around lines 193 - 196, The execute()/get_result()/fetch_column() calls in Updater.php (used by loadGitHubToken and saveGitHubToken) assume success and can silently fail; modify the code around $stmt->execute(), $stmt->get_result(), and $result->fetch_column() to check the boolean/return values, log or throw a clear error when execute() returns false, verify get_result() !== false before using it, handle a false/null fetch_column() appropriately (treat as missing token or error), ensure $stmt->close() always runs (use finally-like cleanup), and propagate failure to the caller so state cannot silently desync.installer/classes/Installer.php (1)
263-265:⚠️ Potential issue | 🔴 CriticalGerman locale mapping is incomplete and still downgrades to Italian.
Line 263 to Line 265 adds
de_DEfor data import, but Line 85 (createEnvFile whitelist) and Line 1096 onward (getInstallerLocale) still force non-it_IT/en_UStoit_IT. Result: a German install can seed German data but persistAPP_LOCALE=it_IT.🔧 Proposed fix
--- a/installer/classes/Installer.php +++ b/installer/classes/Installer.php @@ - if ($normalizedLocale === 'en' || $normalizedLocale === 'en_us') { + if ($normalizedLocale === 'en' || $normalizedLocale === 'en_us') { $normalizedLocale = 'en_US'; + } elseif ($normalizedLocale === 'de' || $normalizedLocale === 'de_de') { + $normalizedLocale = 'de_DE'; } elseif ($normalizedLocale === 'it' || $normalizedLocale === 'it_it') { $normalizedLocale = 'it_IT'; } @@ - if (!in_array($normalizedLocale, ['it_IT', 'en_US'], true)) { + if (!in_array($normalizedLocale, ['it_IT', 'en_US', 'de_DE'], true)) { $normalizedLocale = 'it_IT'; } @@ private function getInstallerLocale(): string { $locale = $this->config['APP_LOCALE'] ?? 'it_IT'; $locale = str_replace('-', '_', strtolower($locale)); if ($locale === 'en' || $locale === 'en_us') { return 'en_US'; } + if ($locale === 'de' || $locale === 'de_de') { + return 'de_DE'; + } if ($locale === 'it' || $locale === 'it_it') { return 'it_IT'; } return 'it_IT'; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@installer/classes/Installer.php` around lines 263 - 265, The installer currently maps German data but then forces APP_LOCALE to it_IT because the createEnvFile whitelist and getInstallerLocale logic override non-it/en locales; update the createEnvFile whitelist to allow 'de_DE' (and equivalent keys like 'de'/'de_de') through when persisting APP_LOCALE, and modify getInstallerLocale to stop defaulting all non-'it_IT'/'en_US' values to 'it_IT'—instead return the actual mapped locale (e.g., 'de_DE') when the installer mapping contains it; adjust any locale-normalization code paths to treat 'de', 'de_de', and 'de_DE' as 'de_DE' so German installs both seed German data and persist APP_LOCALE=de_DE.scripts/manual-upgrade.php (3)
23-23:⚠️ Potential issue | 🟠 MajorBlock execution when default password is unchanged.
Line 23 still uses the known default value; this leaves the upgrader practically guessable if uploaded as-is.
🛡️ Proposed fix
define('UPGRADE_PASSWORD', 'pinakes2026'); +define('UPGRADE_PASSWORD_IS_DEFAULT', UPGRADE_PASSWORD === 'pinakes2026');if ($requestMethod === 'POST' && isset($_POST['password'])) { - if ($_POST['password'] === UPGRADE_PASSWORD) { + if (UPGRADE_PASSWORD_IS_DEFAULT) { + $error = 'SICUREZZA: cambia la password nel file prima di procedere.'; + } elseif ($_POST['password'] === UPGRADE_PASSWORD) { $_SESSION['upgrade_auth'] = true; } else { $error = 'Password errata.'; } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/manual-upgrade.php` at line 23, The script still defines the known default upgrade password via the constant UPGRADE_PASSWORD ('pinakes2026'); stop blind execution by detecting this default at runtime and aborting: replace the hardcoded use with a required configuration (e.g., read from an environment variable) and add a guard in the script startup that checks if UPGRADE_PASSWORD equals 'pinakes2026' (or is empty) and immediately exit with an error message instructing the operator to set a secure password before proceeding.
423-451:⚠️ Potential issue | 🟠 Major
multi_querycan hide statement-level migration failures.Lines 424-451 still use
multi_query+ finalerrno/error, which can miss mid-batch failures and mark a migration as completed incorrectly.#!/bin/bash # Verify migration execution strategy and post-check logic rg -n "multi_query|store_result|next_result|lastErrno|lastError|INSERT IGNORE INTO migrations" scripts/manual-upgrade.php -C4🛠️ Proposed direction
- // Execute multi-statement SQL - if ($db->multi_query($sql)) { - do { - $result = $db->store_result(); - if ($result instanceof mysqli_result) { - $result->free(); - } - } while ($db->next_result()); - } - - // Check for errors (ignore idempotent ones) - $lastError = $db->error; - $lastErrno = $db->errno; + $statements = preg_split('/;\s*[\r\n]+/', $sql) ?: []; + $migrationFailed = false; + $lastError = ''; + $lastErrno = 0; + + foreach ($statements as $stmtSql) { + $stmtSql = trim($stmtSql); + if ($stmtSql === '' || str_starts_with($stmtSql, '--')) { + continue; + } + if (!$db->query($stmtSql)) { + $lastError = $db->error; + $lastErrno = $db->errno; + $migrationFailed = true; + break; + } + } @@ - if ($lastErrno !== 0 && !in_array($lastErrno, $ignorableErrors, true)) { + if ($migrationFailed && !in_array($lastErrno, $ignorableErrors, true)) { $log[] = '[ERROR] Migrazione ' . $migVersion . ' fallita: [' . $lastErrno . '] ' . $lastError; } else {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/manual-upgrade.php` around lines 423 - 451, Replace the current multi-statement execution that uses $db->multi_query, store_result and next_result with per-statement execution and immediate error checks: split the $sql batch into individual statements (or iterate through prepared statements), execute each with $db->query or prepared execution, inspect $db->errno/$db->error after each statement and log/abort the migration on the first non-ignorable error (using the same $ignorableErrors list), and only when all statements succeed insert the migration record via the existing $recStmt (INSERT IGNORE INTO migrations), push $migVersion onto $migrationsRun and log success; ensure you still free results where needed and include the failing statement and its error in the log so mid-batch failures are not hidden by the final errno check.
29-31:⚠️ Potential issue | 🟠 MajorDo not render runtime errors to the browser.
Line 30 enables
display_errors, which can leak filesystem paths and DB details during failures.🛡️ Proposed fix
error_reporting(E_ALL); -ini_set('display_errors', '1'); +ini_set('display_errors', '0'); +ini_set('log_errors', '1');🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/manual-upgrade.php` around lines 29 - 31, The script currently enables display of runtime errors via ini_set('display_errors', '1') which can leak sensitive info; change this to disable browser output by setting ini_set('display_errors', '0') (or remove the call) and ensure errors are logged instead (e.g., ini_set('log_errors','1') and a proper error_log target) while keeping error_reporting(E_ALL) and the max_execution_time setting; update the ini_set call for 'display_errors' and add/verify logging-related ini_set calls so runtime errors are not rendered to the browser.
🧹 Nitpick comments (2)
app/Controllers/UpdateController.php (2)
38-38: Remove unused local variable$data.Line 38 builds
$databut never uses it; this is dead local state.♻️ Proposed fix
- $data = compact('updateInfo', 'requirements', 'history', 'changelog', 'githubTokenMasked', 'hasGithubToken'); require __DIR__ . '/../Views/admin/updates.php';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Controllers/UpdateController.php` at line 38, Remove the unused local variable $data created by the compact(...) call in UpdateController (the assignment "$data = compact('updateInfo', 'requirements', 'history', 'changelog', 'githubTokenMasked', 'hasGithubToken')"); either delete this assignment entirely or replace usages to consume $updateInfo, $requirements, $history, $changelog, $githubTokenMasked, and $hasGithubToken directly so there is no dead local $data left behind.
509-513: Log token save failures for operability.The catch block returns a generic error but drops the exception context, making incident diagnosis harder.
📝 Proposed fix
} catch (\Throwable $e) { + error_log('[UpdateController] saveToken failed: ' . $e->getMessage()); return $this->jsonResponse($response, [ 'success' => false, 'error' => __('Errore nel salvataggio del token') ], 500); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Controllers/UpdateController.php` around lines 509 - 513, In UpdateController's catch block (the one catching \Throwable $e) replace the silent swallow with logging of the exception before returning the jsonResponse: call the application's logger (e.g., $this->logger or app('log')) to record $e->getMessage() and stack trace (e.g., ->error(...) or logger()->error(..., ['exception' => $e])) and then return the same jsonResponse; ensure you reference the existing catch block around the token save logic so the log includes context like "token save failure".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/Routes/web.php`:
- Line 39: Guard against an empty locale set by checking the result of
I18n::getAvailableLocales() when building $supportedLocales and providing a
sensible fallback before the route registration loop: after $supportedLocales =
array_keys(I18n::getAvailableLocales()) verify if $supportedLocales is empty and
if so replace it with a default (e.g. [app()->getLocale()] or ['en']) or log an
error; this ensures the subsequent locale-based route registration (the loop
referencing $supportedLocales) still registers routes instead of skipping them
entirely.
In `@app/Views/admin/updates.php`:
- Around line 552-556: The fetch calls that post the GitHub token (the one
constructing body `csrf_token=...&github_token=...` and the similar call at the
other occurrence) must include the Accept and X-Requested-With AJAX headers to
ensure CSRF failures return JSON; update the request headers object used in
those fetch calls to include 'Accept': 'application/json' and
'X-Requested-With': 'XMLHttpRequest' in addition to the existing 'Content-Type':
'application/x-www-form-urlencoded' so response.json() won't throw on HTML error
pages.
- Line 162: The external GitHub link anchor (the <a> with href
"https://github.com/settings/tokens" in the updates view) opens in a new tab
using target="_blank" but lacks rel="noopener noreferrer"; update that anchor in
app/Views/admin/updates.php to include rel="noopener noreferrer" alongside
target="_blank" to prevent window.opener attacks and improve security.
- Around line 153-155: The icon-only remove button lacks an accessible name;
update the button rendered where removeGitHubToken() is invoked so it exposes a
label to assistive tech (e.g., add aria-label="Remove token" or include a
visually hidden text span alongside the <i class="fas fa-trash..."> icon).
Ensure the change preserves existing classes/onclick and keeps visual appearance
(hidden text or aria-only) while providing a clear, descriptive name for screen
readers.
In `@scripts/manual-upgrade.php`:
- Around line 290-298: The DB backup failure branch currently logs a warning and
continues; change it to abort the upgrade when the backup cannot be created by
checking the same conditions around $exitCode and filesize($backupFile) (the
block that builds $log[] and calls `@unlink`($backupFile)), and replace the
"continue without backup" behavior with a fatal stop (e.g., add a
processLogger/error $log[] entry and call exit(1) or throw an exception) so
execution does not proceed past the failed mysqldump.
- Around line 306-312: The extraction currently calls $zip->extractTo($tempDir)
on untrusted $file['tmp_name'], which allows path traversal; instead iterate
entries via ZipArchive::numFiles and ZipArchive::getNameIndex and validate each
name: reject names starting with '/' or drive letters, and reject any that
contain '..' path segments or resolve outside $tempDir when joined; for valid
entries create required directories under $tempDir and extract/write files only
to the sanitized target paths (or skip/throw on invalid entries) rather than
calling extractTo directly; keep using $zip->open and $zip->close but replace
the direct extractTo call with per-entry validation and safe extraction logic
referencing $zip, $tempDir and $file['tmp_name'].
---
Outside diff comments:
In `@README.md`:
- Line 8: Update the stale "Languages" header string that currently reads
"License: GPL-3 | Languages: Italian, English" to include German (Deutsch) —
e.g., change the header to "License: GPL-3 | Languages: Italian, English,
German (Deutsch)" or similar; ensure the displayed languages match the added
de_DE locale so the README's languages list and the new locale are consistent.
- Line 219: Update the Quick Start step text so the installer language choice
includes German; replace the existing line "Select language (Italian or
English)." in the README's Quick Start section with a version that lists
Italian, English, and German (e.g., "Select language (Italian, English or
German).") so the documentation matches the newly added German locale.
- Line 344: Update the OPAC feature line that currently reads "Responsive,
multilingual frontend (Italian + English)" to include German; locate the exact
string "Responsive, multilingual frontend (Italian + English)" and change it to
something like "Responsive, multilingual frontend (Italian, English, German)" so
the README accurately lists supported frontend locales.
- Around line 362-370: Add a bullet to the Auto-Updater feature list mentioning
the new optional GitHub personal access token support: note that admins can
store a GitHub API token in system_settings (configurable from Admin → Updates)
to raise GitHub API rate limits and avoid rate-limit errors when using the
built-in update system. Ensure the text matches the style of the other bullets
and briefly states where to configure the token and its purpose.
---
Duplicate comments:
In `@app/Support/Updater.php`:
- Around line 186-200: The code reads and stores the GitHub token in plaintext;
add symmetric encryption/decryption around persistence: implement private
methods encryptUpdaterSecret(string $plain): string and
decryptUpdaterSecret(string $cipher): string that use the application key (from
config/env) and a secure crypto library, then update the Updater DB write path
to call encryptUpdaterSecret before binding/storing the setting and update the
read path (the block using $stmt->bind_param / $stmt->execute /
$result->fetch_column and the later similar block around lines 224-241) to
retrieve the stored value and call decryptUpdaterSecret before assigning
$this->githubToken; ensure existing code handles decryption failures gracefully
and keep DB column as ciphertext.
- Around line 193-196: The execute()/get_result()/fetch_column() calls in
Updater.php (used by loadGitHubToken and saveGitHubToken) assume success and can
silently fail; modify the code around $stmt->execute(), $stmt->get_result(), and
$result->fetch_column() to check the boolean/return values, log or throw a clear
error when execute() returns false, verify get_result() !== false before using
it, handle a false/null fetch_column() appropriately (treat as missing token or
error), ensure $stmt->close() always runs (use finally-like cleanup), and
propagate failure to the caller so state cannot silently desync.
In `@installer/classes/Installer.php`:
- Around line 263-265: The installer currently maps German data but then forces
APP_LOCALE to it_IT because the createEnvFile whitelist and getInstallerLocale
logic override non-it/en locales; update the createEnvFile whitelist to allow
'de_DE' (and equivalent keys like 'de'/'de_de') through when persisting
APP_LOCALE, and modify getInstallerLocale to stop defaulting all
non-'it_IT'/'en_US' values to 'it_IT'—instead return the actual mapped locale
(e.g., 'de_DE') when the installer mapping contains it; adjust any
locale-normalization code paths to treat 'de', 'de_de', and 'de_DE' as 'de_DE'
so German installs both seed German data and persist APP_LOCALE=de_DE.
In `@installer/database/data_de_DE.sql`:
- Around line 7-342: The seed file uses plain INSERTs with explicit IDs (e.g.
INSERT INTO `generi` VALUES (...), INSERT INTO `cms_pages`, INSERT INTO
`email_templates`, INSERT INTO `languages`, INSERT INTO `home_content`, INSERT
INTO `themes`) which will fail on reruns due to duplicate keys; make these
statements idempotent by converting them to either INSERT ... ON DUPLICATE KEY
UPDATE (updating relevant columns like setting_value/description/updated_at or
content fields) or INSERT IGNORE where overwriting isn't desired, and apply the
same pattern to the bulk generi inserts and other explicit-id inserts so the
script can be safely re-run without error.
In `@README.md`:
- Around line 28-45: The v0.4.9.2 changelog entry in README.md is missing
several items; update the release notes under the "🏷️ Genre Management, Book
Filters & Bug Fixes" (or the v0.4.9.2 section) to include entries for the German
locale addition, dynamic locale route registration, the GitHub API token support
for the updater, and the manual upgrade script; add concise bullet points
matching the existing style and wording so the changelog reflects all features
shipped in this PR.
In `@scripts/manual-upgrade.php`:
- Line 23: The script still defines the known default upgrade password via the
constant UPGRADE_PASSWORD ('pinakes2026'); stop blind execution by detecting
this default at runtime and aborting: replace the hardcoded use with a required
configuration (e.g., read from an environment variable) and add a guard in the
script startup that checks if UPGRADE_PASSWORD equals 'pinakes2026' (or is
empty) and immediately exit with an error message instructing the operator to
set a secure password before proceeding.
- Around line 423-451: Replace the current multi-statement execution that uses
$db->multi_query, store_result and next_result with per-statement execution and
immediate error checks: split the $sql batch into individual statements (or
iterate through prepared statements), execute each with $db->query or prepared
execution, inspect $db->errno/$db->error after each statement and log/abort the
migration on the first non-ignorable error (using the same $ignorableErrors
list), and only when all statements succeed insert the migration record via the
existing $recStmt (INSERT IGNORE INTO migrations), push $migVersion onto
$migrationsRun and log success; ensure you still free results where needed and
include the failing statement and its error in the log so mid-batch failures are
not hidden by the final errno check.
- Around line 29-31: The script currently enables display of runtime errors via
ini_set('display_errors', '1') which can leak sensitive info; change this to
disable browser output by setting ini_set('display_errors', '0') (or remove the
call) and ensure errors are logged instead (e.g., ini_set('log_errors','1') and
a proper error_log target) while keeping error_reporting(E_ALL) and the
max_execution_time setting; update the ini_set call for 'display_errors' and
add/verify logging-related ini_set calls so runtime errors are not rendered to
the browser.
---
Nitpick comments:
In `@app/Controllers/UpdateController.php`:
- Line 38: Remove the unused local variable $data created by the compact(...)
call in UpdateController (the assignment "$data = compact('updateInfo',
'requirements', 'history', 'changelog', 'githubTokenMasked',
'hasGithubToken')"); either delete this assignment entirely or replace usages to
consume $updateInfo, $requirements, $history, $changelog, $githubTokenMasked,
and $hasGithubToken directly so there is no dead local $data left behind.
- Around line 509-513: In UpdateController's catch block (the one catching
\Throwable $e) replace the silent swallow with logging of the exception before
returning the jsonResponse: call the application's logger (e.g., $this->logger
or app('log')) to record $e->getMessage() and stack trace (e.g., ->error(...) or
logger()->error(..., ['exception' => $e])) and then return the same
jsonResponse; ensure you reference the existing catch block around the token
save logic so the log includes context like "token save failure".
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
README.mdapp/Controllers/UpdateController.phpapp/Routes/web.phpapp/Support/I18n.phpapp/Support/Updater.phpapp/Views/admin/updates.phpinstaller/classes/Installer.phpinstaller/database/data_de_DE.sqlinstaller/index.phpinstaller/steps/step0.phplocale/de_DE.jsonlocale/routes_de_DE.jsonscripts/manual-upgrade.php
README.md: - Add Deutsch to language header, Quick Start, OPAC description - Add German locale and updater improvements to v0.4.9.2 changelog - Add GitHub API token bullet to Auto-Updater features installer/classes/Installer.php: - Add de_DE to createEnvFile locale whitelist and normalization - Add de_DE to getInstallerLocale() so German installs persist APP_LOCALE=de_DE installer/database/data_de_DE.sql: - Make all INSERT statements idempotent (INSERT IGNORE / ON DUPLICATE KEY UPDATE) scripts/manual-upgrade.php: - Disable display_errors, enable log_errors - Block execution when default password is unchanged - Abort upgrade on DB backup failure (mandatory backup) - Validate ZIP entries for path traversal before extraction - Replace multi_query with per-statement execution for migration error detection app/Routes/web.php: - Guard against empty locale set with fallback to installation locale app/Support/Updater.php: - Add error handling for execute()/get_result() in token load/save app/Controllers/UpdateController.php: - Log token save failures via SecureLogger app/Views/admin/updates.php: - Add aria-label/title to icon-only remove button - Add rel="noopener noreferrer" to external GitHub link - Add Accept/X-Requested-With headers to token fetch calls
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/Views/admin/updates.php (1)
289-289:⚠️ Potential issue | 🟡 MinorExternal link missing
rel="noopener noreferrer".The GitHub releases link on line 289 uses
target="_blank"withoutrel="noopener noreferrer", while the same fix was applied to the tokens link on line 162.🔐 Proposed fix
- <p class="mt-1"><?= __("Vai alla") ?> <a href="https://github.com/fabiodalez-dev/Pinakes/releases" target="_blank" class="text-green-600 hover:text-green-700 underline">pagina releases</a> <?= __("e scarica il file") ?> <code class="bg-gray-100 px-1 rounded text-xs">pinakes-vX.X.X.zip</code></p> + <p class="mt-1"><?= __("Vai alla") ?> <a href="https://github.com/fabiodalez-dev/Pinakes/releases" target="_blank" rel="noopener noreferrer" class="text-green-600 hover:text-green-700 underline">pagina releases</a> <?= __("e scarica il file") ?> <code class="bg-gray-100 px-1 rounded text-xs">pinakes-vX.X.X.zip</code></p>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Views/admin/updates.php` at line 289, The anchor element that renders the GitHub releases link in the updates view (the <a href="https://github.com/fabiodalez-dev/Pinakes/releases" target="_blank" ...> anchor near the "Vai alla" text) opens a new tab without rel="noopener noreferrer"; update that anchor to include rel="noopener noreferrer" (matching the tokens link fix already applied elsewhere) to prevent window.opener-based attacks and ensure safe external linking.
♻️ Duplicate comments (5)
installer/database/data_de_DE.sql (1)
4-347:⚠️ Potential issue | 🟠 Major
FOREIGN_KEY_CHECKScan remain disabled if the script aborts mid-run.If execution stops before the final restore, the session keeps FK checks off. This was previously raised and still applies.
Hardening option
-SET FOREIGN_KEY_CHECKS=0; +-- Prefer wrapping in a procedure with an EXIT HANDLER that always restores: +-- 1) SET FOREIGN_KEY_CHECKS=0 +-- 2) run seed statements +-- 3) SET FOREIGN_KEY_CHECKS=1 in both success and SQLEXCEPTION paths🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@installer/database/data_de_DE.sql` around lines 4 - 347, The script leaves FOREIGN_KEY_CHECKS disabled for the whole run which can persist if execution aborts; instead ensure FK checks are restored no matter what by (1) removing the global SET FOREIGN_KEY_CHECKS=0/1 pair at the top/footer and (2) if you must disable constraints, scope the disable to the minimal block(s) that require it (surround only those INSERT IGNORE INTO `generi` / other problematic statements with SET FOREIGN_KEY_CHECKS=0; ... SET FOREIGN_KEY_CHECKS=1;) and (3) add an explicit safety guard near the top of the script that forces SET FOREIGN_KEY_CHECKS=1; to ensure checks are enabled if a previous run aborted; look for the existing FOREIGN_KEY_CHECKS lines to update.app/Support/Updater.php (1)
205-207:⚠️ Potential issue | 🟠 MajorEncrypt the GitHub token before storing it.
At Line 205 and Line 243, the token is still read/written as plaintext in
system_settings.setting_value. DB dumps/backups would expose a live credential.🔐 Suggested direction
- if (is_string($value) && $value !== '') { - $this->githubToken = $value; + if (is_string($value) && trim($value) !== '') { + $decrypted = $this->decryptUpdaterSecret($value); + if ($decrypted !== '') { + $this->githubToken = $decrypted; + } }- $stmt->bind_param('sss', $cat, $key, $token); + $storedToken = $token === '' ? '' : $this->encryptUpdaterSecret($token); + $stmt->bind_param('sss', $cat, $key, $storedToken);private function encryptUpdaterSecret(string $plain): string { /* app-key backed encryption */ } private function decryptUpdaterSecret(string $cipher): string { /* return '' on invalid/corrupt */ }Also applies to: 231-252
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Support/Updater.php` around lines 205 - 207, The GitHub token is currently stored and read in plaintext into $this->githubToken and saved into system_settings.setting_value; implement app-key backed encryption by adding helper methods encryptUpdaterSecret(string $plain): string and decryptUpdaterSecret(string $cipher): string (decrypt should return '' on invalid/corrupt data) and update the setter/reader logic to call encryptUpdaterSecret before persisting the token and decryptUpdaterSecret when loading it into $this->githubToken (ensure all places that write/read the token in Updater.php use these helpers).scripts/manual-upgrade.php (1)
320-329:⚠️ Potential issue | 🟠 MajorZIP path validation is incomplete for Windows-style absolute paths.
At Line 321-324, traversal checks cover
/and../, but not entries likeC:/.... WithextractTo()at Line 328, that can still escape the temp target on Windows hosts.#!/bin/bash # Verify current ZIP entry guards and check whether drive-letter guard exists. rg -n "getNameIndex|str_starts_with\\(\\$entry, '/\\)|str_contains\\(\\$entry, '\\.\\./'|extractTo\\(\\$tempDir\\)" scripts/manual-upgrade.php -C3 rg -n "A-Za-z\\]:\\[\\\\/\\]" scripts/manual-upgrade.php # Expected: first command shows current checks; second should return no matches (missing drive-letter guard).🛡️ Minimal hardening patch
- if ( - str_starts_with($entry, '/') - || str_contains($entry, '../') - || str_contains($entry, '..\\') - ) { + if (preg_match('#(^/|^[A-Za-z]:/|(^|/)\.\.(/|$))#', $entry)) { throw new RuntimeException('ZIP non valido: contiene percorsi pericolosi'); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/manual-upgrade.php` around lines 320 - 329, The ZIP entry validation misses Windows absolute paths and leading backslashes; update the guard that checks $entry (used with str_starts_with/str_contains and before $zip->extractTo($tempDir)) to also reject entries that start with a backslash and entries matching a Windows drive-letter absolute path (e.g. pattern like /^[A-Za-z]:[\/\\]/). Throw the same RuntimeException when such entries are detected (referencing $entry, str_starts_with, str_contains, and extractTo) so extractTo cannot escape the target on Windows.app/Routes/web.php (1)
39-42: LGTM — empty-locale fallback properly addresses the earlier concern.The guard correctly prevents route registration from being skipped when the DB returns no languages, falling back to the installation locale or
it_IT.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Routes/web.php` around lines 39 - 42, No change required: the fallback logic around $supportedLocales correctly uses array_keys(I18n::getAvailableLocales()) and substitutes [$installationLocale ?: 'it_IT'] when empty; leave the $supportedLocales assignment and guard as-is to ensure routes register even when the DB returns no locales.app/Views/admin/updates.php (1)
119-170: Past review findings (accessibility,rel="noopener", AJAX headers) have all been addressed.
- Line 153:
aria-labelandtitleon the remove button ✓- Line 162:
rel="noopener noreferrer"on the external link ✓- Lines 554-557, 596-599:
Accept: application/jsonandX-Requested-Withheaders ✓🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Views/admin/updates.php` around lines 119 - 170, The review comment includes a stray duplicate marker ([duplicate_comment]) after the approval tokens; remove that duplicate marker so only the intended [approve_code_changes] remains in the review text, no code edits required—verify the referenced UI elements (removeGitHubToken() button aria/title, the external link with rel="noopener noreferrer", and the AJAX headers) remain unchanged.
🧹 Nitpick comments (1)
app/Routes/web.php (1)
601-704: Legacy redirect blocks only handleen_USandit_IT— no coverage forde_DE(or future locales).With dynamic locale registration, new locales like
de_DEare now automatically supported for route generation, but the legacy redirect mechanism here is hard-coded to two locales. A German installation won't get redirects from English or Italian URLs to the canonical German ones.This isn't a bug today (there are no legacy German URLs in the wild), but as more locales are added the pattern doesn't scale. Consider a data-driven approach or at minimum a comment noting the limitation.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Routes/web.php` around lines 601 - 704, The legacy-redirect logic is hard-coded for only en_US and it_IT; replace it with a data-driven approach by extracting the legacyRedirects arrays into a mapping keyed by source locale (e.g., $legacyRedirectMap = ['en_US' => [...], 'it_IT' => [...]]), then iterate over each $sourceLocale => $legacyRedirects and, for any $sourceLocale !== $installationLocale, register the redirects using RouteTranslator::route($routeKey) and $registerRouteIfUnique('GET', $legacyPath, ...) as currently used; alternatively, if you intentionally want to limit redirects, add a clear comment near $installationLocale and the existing blocks explaining the limitation. Ensure you reference and update the blocks that currently use $legacyRedirects, RouteTranslator::route, $installationLocale, and $registerRouteIfUnique.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/Views/admin/updates.php`:
- Around line 145-148: The placeholder for the github-token input is invisible
because it's a password field; change the UI so the masked value is visible:
when $hasGithubToken is true, render the input with type="text" (or render a
separate read-only element) so HtmlHelper::e($githubTokenMasked) is readable—use
the id="github-token" to locate the element and adjust its type or add a sibling
read-only span/input to show $githubTokenMasked while keeping the
editable/secret input behavior (autocomplete="off") for actual token entry.
In `@installer/database/data_de_DE.sql`:
- Around line 313-317: The INSERT ... ON DUPLICATE KEY UPDATE for the languages
table only updates name/is_active/is_default, leaving translation_file and the
completion counters stale; modify the ON DUPLICATE KEY UPDATE clause (for the
INSERT that touches `languages` with columns `translation_file`, `total_keys`,
`translated_keys`, `completion_percentage`) to also assign those columns from
VALUES(...) so that `translation_file`, `total_keys`, `translated_keys`, and
`completion_percentage` are refreshed on upsert.
- Around line 320-333: The INSERT INTO home_content upsert only updates the
content column so rerunning the seed won't refresh translated strings in title,
subtitle, button_text or button_link; modify the ON DUPLICATE KEY UPDATE clause
in this INSERT (the statement inserting rows like 'hero', 'features_title',
'feature_1', etc.) to also update title, subtitle, button_text and button_link =
VALUES(title), VALUES(subtitle), VALUES(button_text), VALUES(button_link)
(keeping content = VALUES(content)) so all translated fields are overwritten on
rerun.
In `@scripts/manual-upgrade.php`:
- Around line 451-474: The loop currently breaks on the first SQL error and
later records the migration as complete even if that first error was ignorable;
change the error handling inside the loop where $db->query($stmtSql) fails so it
checks $lastErrno against the $ignorableErrors list and does NOT break for
ignorable errors (log them and continue), but still sets $migrationFailed = true
and breaks only for non-ignorable errors; then ensure the post-loop logic that
inserts the migration record (using $recStmt and adding to $migrationsRun) only
runs when $migrationFailed is false (i.e., all non-ignorable statements
succeeded), otherwise push an error log and skip recording the migration.
---
Outside diff comments:
In `@app/Views/admin/updates.php`:
- Line 289: The anchor element that renders the GitHub releases link in the
updates view (the <a href="https://github.com/fabiodalez-dev/Pinakes/releases"
target="_blank" ...> anchor near the "Vai alla" text) opens a new tab without
rel="noopener noreferrer"; update that anchor to include rel="noopener
noreferrer" (matching the tokens link fix already applied elsewhere) to prevent
window.opener-based attacks and ensure safe external linking.
---
Duplicate comments:
In `@app/Routes/web.php`:
- Around line 39-42: No change required: the fallback logic around
$supportedLocales correctly uses array_keys(I18n::getAvailableLocales()) and
substitutes [$installationLocale ?: 'it_IT'] when empty; leave the
$supportedLocales assignment and guard as-is to ensure routes register even when
the DB returns no locales.
In `@app/Support/Updater.php`:
- Around line 205-207: The GitHub token is currently stored and read in
plaintext into $this->githubToken and saved into system_settings.setting_value;
implement app-key backed encryption by adding helper methods
encryptUpdaterSecret(string $plain): string and decryptUpdaterSecret(string
$cipher): string (decrypt should return '' on invalid/corrupt data) and update
the setter/reader logic to call encryptUpdaterSecret before persisting the token
and decryptUpdaterSecret when loading it into $this->githubToken (ensure all
places that write/read the token in Updater.php use these helpers).
In `@app/Views/admin/updates.php`:
- Around line 119-170: The review comment includes a stray duplicate marker
([duplicate_comment]) after the approval tokens; remove that duplicate marker so
only the intended [approve_code_changes] remains in the review text, no code
edits required—verify the referenced UI elements (removeGitHubToken() button
aria/title, the external link with rel="noopener noreferrer", and the AJAX
headers) remain unchanged.
In `@installer/database/data_de_DE.sql`:
- Around line 4-347: The script leaves FOREIGN_KEY_CHECKS disabled for the whole
run which can persist if execution aborts; instead ensure FK checks are restored
no matter what by (1) removing the global SET FOREIGN_KEY_CHECKS=0/1 pair at the
top/footer and (2) if you must disable constraints, scope the disable to the
minimal block(s) that require it (surround only those INSERT IGNORE INTO
`generi` / other problematic statements with SET FOREIGN_KEY_CHECKS=0; ... SET
FOREIGN_KEY_CHECKS=1;) and (3) add an explicit safety guard near the top of the
script that forces SET FOREIGN_KEY_CHECKS=1; to ensure checks are enabled if a
previous run aborted; look for the existing FOREIGN_KEY_CHECKS lines to update.
In `@scripts/manual-upgrade.php`:
- Around line 320-329: The ZIP entry validation misses Windows absolute paths
and leading backslashes; update the guard that checks $entry (used with
str_starts_with/str_contains and before $zip->extractTo($tempDir)) to also
reject entries that start with a backslash and entries matching a Windows
drive-letter absolute path (e.g. pattern like /^[A-Za-z]:[\/\\]/). Throw the
same RuntimeException when such entries are detected (referencing $entry,
str_starts_with, str_contains, and extractTo) so extractTo cannot escape the
target on Windows.
---
Nitpick comments:
In `@app/Routes/web.php`:
- Around line 601-704: The legacy-redirect logic is hard-coded for only en_US
and it_IT; replace it with a data-driven approach by extracting the
legacyRedirects arrays into a mapping keyed by source locale (e.g.,
$legacyRedirectMap = ['en_US' => [...], 'it_IT' => [...]]), then iterate over
each $sourceLocale => $legacyRedirects and, for any $sourceLocale !==
$installationLocale, register the redirects using
RouteTranslator::route($routeKey) and $registerRouteIfUnique('GET', $legacyPath,
...) as currently used; alternatively, if you intentionally want to limit
redirects, add a clear comment near $installationLocale and the existing blocks
explaining the limitation. Ensure you reference and update the blocks that
currently use $legacyRedirects, RouteTranslator::route, $installationLocale, and
$registerRouteIfUnique.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
README.mdapp/Controllers/UpdateController.phpapp/Routes/web.phpapp/Support/Updater.phpapp/Views/admin/updates.phpinstaller/classes/Installer.phpinstaller/database/data_de_DE.sqlscripts/manual-upgrade.php
🚧 Files skipped from review as they are similar to previous changes (1)
- installer/classes/Installer.php
updates.php: - Add rel="noopener noreferrer" to GitHub releases link (line 289) - Show masked token as readable text, switch to password on focus data_de_DE.sql: - Expand languages upsert to refresh all metadata columns - Expand home_content upsert to refresh title/subtitle/button fields - Add FK_CHECKS=1 safety guard at script start manual-upgrade.php: - Harden ZIP path validation with Windows drive-letter check - Fix migration: ignorable errors continue execution, only fatal errors break
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/manual-upgrade.php`:
- Around line 461-472: When a migration fails the code currently only appends to
$log and continues; change this so the upgrade aborts on failure: either
immediately stop execution after logging by calling exit(1) (or throw an
Exception) when $migrationFailed is true, or set a persistent failure flag (e.g.
$upgradeFailed = true) and after the migration loop check that flag before
printing the final success message and exit with non-zero status; reference the
$migrationFailed check, the $log array, $migrationsRun handling and the final
success message to ensure the script never prints success or returns zero when
any migration failed.
- Around line 186-190: The copy() call that writes $srcPath to $dstPath is
unchecked and $count is incremented regardless; change the logic in the block
around copy($srcPath, $dstPath) so you check its boolean return value, only
increment $count when copy(...) returns true, and handle failures (e.g., log an
error/throw an exception or collect a failure list) instead of silently
proceeding; keep the existing is_dir(dirname($dstPath)) / mkdir(...) behavior
but ensure error handling uses the same context variables ($srcPath, $dstPath,
$count) so partial upgrade failures are not masked.
- Line 93: Enabling mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT)
causes SQL errors to throw mysqli_sql_exception and bypass the existing
ignorable-error handling around $db->query(); wrap the database query execution
where ignorable errors are checked (the block that inspects $db->query() result
around the existing ignorable-errors logic) in a try-catch that catches
mysqli_sql_exception, examine the exception message/code for the same ignorable
cases (duplicate column/table/index, already exists, etc.), and if it matches
treat it as the original false-return path (log/skip) otherwise rethrow or
abort; ensure this change keeps mysqli_report(...) as-is and only adds the
try-catch around the $db->query() calls that previously relied on false returns.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
app/Views/admin/updates.phpinstaller/database/data_de_DE.sqlscripts/manual-upgrade.php
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (3)
scripts/manual-upgrade.php (2)
186-190:⚠️ Potential issue | 🟠 MajorDo not count files as copied when
copy()fails.At Line 189,
copy($srcPath, $dstPath)is unchecked and Line 190 increments$countanyway, which can mask partial upgrade failures.Proposed fix
- copy($srcPath, $dstPath); - $count++; + if (!copy($srcPath, $dstPath)) { + throw new RuntimeException('Copia file fallita: ' . $srcPath . ' -> ' . $dstPath); + } + $count++;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/manual-upgrade.php` around lines 186 - 190, The code currently calls copy($srcPath, $dstPath) and increments $count unconditionally; change this so the return value of copy() is checked and $count is only incremented when copy returns true. Locate the block around copy($srcPath, $dstPath) that uses $srcPath, $dstPath and $count, and if copy fails handle the error (e.g. log/throw/collect failures) instead of incrementing $count to avoid masking partial upgrade failures.
445-472:⚠️ Potential issue | 🔴 CriticalMigration error handling is inconsistent and can still report a successful upgrade.
Two coupled problems remain:
- With
MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT, SQL errors throw instead of returningfalse, so the ignorable-error branch at Line 450 is bypassed.- If
$migrationFailedis true, Line 462 only logs and the script can still end with success at Line 513.This can leave a partially migrated DB while signaling success.
In PHP MySQLi, when mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT) is enabled, does mysqli::query() return false on SQL error or throw mysqli_sql_exception?Proposed fix
foreach ($statements as $stmtSql) { $stmtSql = trim($stmtSql); if ($stmtSql === '' || str_starts_with($stmtSql, '--')) { continue; } - if (!$db->query($stmtSql)) { - $lastError = $db->error; - $lastErrno = $db->errno; - if (in_array($lastErrno, $ignorableErrors, true)) { - continue; - } - $migrationFailed = true; - break; - } + try { + $db->query($stmtSql); + } catch (mysqli_sql_exception $ex) { + $lastErrno = (int) $ex->getCode(); + $lastError = $ex->getMessage(); + if (in_array($lastErrno, $ignorableErrors, true)) { + continue; + } + $migrationFailed = true; + break; + } } if ($migrationFailed) { - $log[] = '[ERROR] Migrazione ' . $migVersion . ' fallita: [' . $lastErrno . '] ' . $lastError; + throw new RuntimeException('Migrazione ' . $migVersion . ' fallita: [' . $lastErrno . '] ' . $lastError); } else {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/manual-upgrade.php` around lines 445 - 472, The migration loop currently assumes $db->query returns false on error and sets $migrationFailed, but with mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT) enabled SQL errors throw mysqli_sql_exception and bypass the ignorable-errors branch; update the logic around the execution of statements (the foreach that uses $db->query) to either temporarily suppress strict reporting or wrap each execution in a try/catch that catches mysqli_sql_exception, extracts errno/message (use $e->getCode() and $e->getMessage()) to compare against $ignorableErrors and only set $migrationFailed when appropriate; ensure that when $migrationFailed is true you never run the migration recording block (the prepare/execute/close using $recStmt and pushing $migrationsRun) and that the script exits/fails (non-zero) or ultimately reports overall failure instead of success so partial migrations are not signaled as completed.app/Support/Updater.php (1)
205-207:⚠️ Potential issue | 🟠 MajorPersisting GitHub PAT in plaintext is still a credential-exposure risk.
github_tokenis read/written as clear text, so DB dumps or low-privileged DB reads expose a live secret. Please encrypt at rest before writing tosystem_settings, and decrypt only when constructing auth headers in memory.Also applies to: 231-252
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Support/Updater.php` around lines 205 - 207, The github token is being stored as plaintext in the updater (property $this->githubToken) which risks credential exposure; change the write path so the value is encrypted before persisting to system_settings and change all read/usage paths so decryption happens only in-memory when constructing auth headers (e.g., when building Authorization headers or any method that reads $this->githubToken). Specifically: encrypt the token before assigning/persisting in the Updater class (where $this->githubToken is set) and decrypt right before use (e.g., in the method that builds HTTP auth headers or any function that references $this->githubToken); apply the same change to the other block referenced (lines 231–252) to ensure stored values are encrypted at rest and only decrypted in memory.
🧹 Nitpick comments (1)
installer/database/data_de_DE.sql (1)
4-5: Remove redundant FOREIGN_KEY_CHECKS toggle preface.Line 4 (
SET FOREIGN_KEY_CHECKS=1;) is immediately overridden by Line 5, so it adds noise without effect.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@installer/database/data_de_DE.sql` around lines 4 - 5, Remove the redundant preface statement "SET FOREIGN_KEY_CHECKS=1;" since it is immediately overridden by the following "SET FOREIGN_KEY_CHECKS=0;"; delete the line containing SET FOREIGN_KEY_CHECKS=1; so the script only toggles FOREIGN_KEY_CHECKS to 0 where intended and avoids confusing/noisy statements.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/Controllers/UpdateController.php`:
- Around line 500-503: Updater::saveGitHubToken currently persists the raw PAT
into system_settings.setting_value; change it to store an encrypted value
instead (or move storage to a secure secrets backend) so backups/dumps don't
expose plaintext. Update Updater::saveGitHubToken to encrypt the token with the
app's encryption service/key before writing, update the corresponding reader
(e.g., Updater::getGitHubToken or wherever the value is read) to decrypt on
load, and ensure any DB column usage still references
system_settings.setting_value only for ciphertext (or switch to a dedicated
secrets table/vault integration) and add tests verifying encryption/decryption
round-trip and that plaintext is never written.
In `@installer/classes/Installer.php`:
- Around line 77-88: The locale normalization currently lowercases but doesn't
convert hyphens to underscores so inputs like "de-DE" bypass the mapping and hit
the fallback; update the normalization of $locale in Installer.php by replacing
hyphens with underscores (e.g., $normalizedLocale = str_replace('-', '_',
strtolower((string)$locale));) before the conditional mapping for 'en', 'en_us',
'de', 'de_de', 'it', 'it_it', and ensure the in_array check still tests against
the canonical targets ['it_IT','en_US','de_DE'] so mapped values match the
fallback check.
In `@installer/index.php`:
- Around line 36-41: The locale normalization is inconsistent: $locale is
normalized to $localeCode using a match expression before building
$translationFile but the same short-form mapping isn't applied when setting the
HTML lang attribute later; unify by extracting the normalization into a single
helper (e.g., normalizeLocale) and use it in both places so 'en' and 'de'
produce the same canonical form for $translationFile and the HTML lang attribute
(update uses of $locale, $localeCode and the HTML-lang assignment to call the
helper).
In `@README.md`:
- Line 8: The tagline line "License: GPL-3 | Languages: Italian, English,
Deutsch" uses mixed language names; update that line in README.md to be
consistent—either change "Deutsch" to "German" to keep all names in English, or
change "Italian, English, Deutsch" to native names like "Italiano, English,
Deutsch" (or better yet "Italiano, English, Deutsch" making all native) and save
the file.
---
Duplicate comments:
In `@app/Support/Updater.php`:
- Around line 205-207: The github token is being stored as plaintext in the
updater (property $this->githubToken) which risks credential exposure; change
the write path so the value is encrypted before persisting to system_settings
and change all read/usage paths so decryption happens only in-memory when
constructing auth headers (e.g., when building Authorization headers or any
method that reads $this->githubToken). Specifically: encrypt the token before
assigning/persisting in the Updater class (where $this->githubToken is set) and
decrypt right before use (e.g., in the method that builds HTTP auth headers or
any function that references $this->githubToken); apply the same change to the
other block referenced (lines 231–252) to ensure stored values are encrypted at
rest and only decrypted in memory.
In `@scripts/manual-upgrade.php`:
- Around line 186-190: The code currently calls copy($srcPath, $dstPath) and
increments $count unconditionally; change this so the return value of copy() is
checked and $count is only incremented when copy returns true. Locate the block
around copy($srcPath, $dstPath) that uses $srcPath, $dstPath and $count, and if
copy fails handle the error (e.g. log/throw/collect failures) instead of
incrementing $count to avoid masking partial upgrade failures.
- Around line 445-472: The migration loop currently assumes $db->query returns
false on error and sets $migrationFailed, but with
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT) enabled SQL errors
throw mysqli_sql_exception and bypass the ignorable-errors branch; update the
logic around the execution of statements (the foreach that uses $db->query) to
either temporarily suppress strict reporting or wrap each execution in a
try/catch that catches mysqli_sql_exception, extracts errno/message (use
$e->getCode() and $e->getMessage()) to compare against $ignorableErrors and only
set $migrationFailed when appropriate; ensure that when $migrationFailed is true
you never run the migration recording block (the prepare/execute/close using
$recStmt and pushing $migrationsRun) and that the script exits/fails (non-zero)
or ultimately reports overall failure instead of success so partial migrations
are not signaled as completed.
---
Nitpick comments:
In `@installer/database/data_de_DE.sql`:
- Around line 4-5: Remove the redundant preface statement "SET
FOREIGN_KEY_CHECKS=1;" since it is immediately overridden by the following "SET
FOREIGN_KEY_CHECKS=0;"; delete the line containing SET FOREIGN_KEY_CHECKS=1; so
the script only toggles FOREIGN_KEY_CHECKS to 0 where intended and avoids
confusing/noisy statements.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
README.mdapp/Controllers/UpdateController.phpapp/Routes/web.phpapp/Support/I18n.phpapp/Support/Updater.phpapp/Views/admin/updates.phpinstaller/classes/Installer.phpinstaller/database/data_de_DE.sqlinstaller/index.phpinstaller/steps/step0.phplocale/de_DE.jsonlocale/routes_de_DE.jsonscripts/manual-upgrade.php
| try { | ||
| $updater = new Updater($db); | ||
| $updater->saveGitHubToken($token); | ||
|
|
There was a problem hiding this comment.
Persisting GitHub PAT in plaintext is a security posture gap.
This path stores the raw token via Updater::saveGitHubToken, and that implementation writes directly to system_settings.setting_value. DB backups/dumps would expose the credential.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/Controllers/UpdateController.php` around lines 500 - 503,
Updater::saveGitHubToken currently persists the raw PAT into
system_settings.setting_value; change it to store an encrypted value instead (or
move storage to a secure secrets backend) so backups/dumps don't expose
plaintext. Update Updater::saveGitHubToken to encrypt the token with the app's
encryption service/key before writing, update the corresponding reader (e.g.,
Updater::getGitHubToken or wherever the value is read) to decrypt on load, and
ensure any DB column usage still references system_settings.setting_value only
for ciphertext (or switch to a dedicated secrets table/vault integration) and
add tests verifying encryption/decryption round-trip and that plaintext is never
written.
manual-upgrade.php: - Check copy() return value, throw on failure - Wrap migration queries in try/catch for mysqli_sql_exception (MYSQLI_REPORT_STRICT throws instead of returning false) - Abort upgrade on migration failure (throw RuntimeException) Installer.php: - Normalize hyphens to underscores in locale (de-DE -> de_DE) installer/index.php: - Handle short-form locale codes in HTML lang mapping data_de_DE.sql: - Remove redundant FK_CHECKS=1 preface README.md: - Use consistent English names (German, not Deutsch)
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (2)
app/Support/Updater.php (1)
205-207:⚠️ Potential issue | 🟠 MajorStore the GitHub token encrypted at rest (currently plaintext).
At Line [205] and Line [251], the token is read/written directly in cleartext. That exposes a live credential in DB dumps and backups.
🔐 Suggested fix
@@ - if (is_string($value) && $value !== '') { - $this->githubToken = $value; + if (is_string($value) && trim($value) !== '') { + $decrypted = $this->decryptUpdaterSecret($value); + if ($decrypted !== '') { + $this->githubToken = $decrypted; + } } @@ - $stmt->bind_param('sss', $cat, $key, $token); + $encryptedToken = $token === '' ? '' : $this->encryptUpdaterSecret($token); + $stmt->bind_param('sss', $cat, $key, $encryptedToken); @@ $this->githubToken = $token; } + + private function encryptUpdaterSecret(string $plain): string + { + // Use your app key (e.g. PLUGIN_ENCRYPTION_KEY) and authenticated encryption. + // Return base64-encoded ciphertext with nonce/tag payload. + } + + private function decryptUpdaterSecret(string $cipher): string + { + // Reverse of encryptUpdaterSecret(); return '' on invalid payload/decrypt failure. + }Also applies to: 231-252
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Support/Updater.php` around lines 205 - 207, The GitHub token is being stored in plaintext via assignments to $this->githubToken; change the write path (e.g., setGithubToken or where $this->githubToken is assigned at the shown snippet) to encrypt the token before persisting (use your app's encryption API, e.g., Laravel's Crypt::encryptString) and change the read path (where the token is read back, e.g., getGithubToken or the code at lines ~231-252) to decrypt it (Crypt::decryptString) before use; ensure the stored property/DB column holds the encrypted string, handle empty strings/null the same as today, and add try/catch around decrypt to surface errors rather than returning raw ciphertext.app/Controllers/UpdateController.php (1)
500-503:⚠️ Potential issue | 🟠 MajorPersisting PAT in plaintext remains a security gap.
This endpoint still stores the raw token through
Updater::saveGitHubToken(...); DB dumps/backups can expose credentials. Please encrypt-at-rest before persisting and decrypt only when constructing outbound GitHub headers.#!/bin/bash # Verify how GitHub token is persisted in Updater implementation. rg -n -C4 "function saveGitHubToken|github_token|setting_value|INSERT INTO system_settings|UPDATE system_settings" app/Support/Updater.php🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Controllers/UpdateController.php` around lines 500 - 503, The code stores the GitHub PAT in plaintext via Updater::saveGitHubToken, so change persistence to encrypt-at-rest: update Updater::saveGitHubToken (or its caller) to encrypt the token using the app's centralized encryption service/key before writing to the DB and mark the setting as encrypted; then only decrypt inside the code path that constructs outbound GitHub headers (e.g., Updater::getGitHubHeaders / Updater::buildAuthHeaders) so Authorization headers are built from a decrypted value in memory, not persisted in plaintext. Ensure you use the existing application encryption API/config (app key or crypto service) and preserve backwards compatibility by detecting/handling previously stored plaintext values where necessary.
🧹 Nitpick comments (1)
app/Views/admin/updates.php (1)
553-563: Add aContent-Typepre-check beforeresponse.json()— same pattern used instartUpdate().
startUpdate()(line 758) already guards against non-JSON responses before calling.json(). Both new token functions lack this, so a CSRF failure or server error returning an HTML error page will surface a rawSyntaxError: Unexpected token '<'instead of a meaningful message.♻️ Proposed fix (identical pattern for both functions)
const response = await fetch(window.BASE_PATH + '/admin/updates/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, body: `csrf_token=${encodeURIComponent(csrfToken)}&github_token=${encodeURIComponent(token)}` }); + + const contentType = response.headers.get('content-type') || ''; + if (!contentType.includes('application/json')) { + const text = await response.text(); + console.error('Server returned non-JSON response:', text.substring(0, 500)); + throw new Error(<?= json_encode(__("Il server ha restituito una risposta non valida. Controlla i log per dettagli."), JSON_HEX_TAG) ?>); + } const data = await response.json();Also applies to: 595-605
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Views/admin/updates.php` around lines 553 - 563, The token POST handlers that fetch '/admin/updates/token' (the fetch block constructing body with csrf_token and github_token) and the similar fetch at lines 595-605 must mirror startUpdate(): before calling response.json() check response.headers.get('content-type') includes 'application/json'; if not, await response.text() and throw or surface a clear error containing that text (or response.status) instead of calling .json(), so CSRF/server HTML errors don't trigger a SyntaxError. Update both token-fetching functions to perform this content-type guard and handle non-JSON responses as startUpdate() does.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@README.md`:
- Line 228: Replace inconsistent language name "Deutsch" with the English term
"German" throughout the README to match the document's English style; update the
occurrences in the phrase "Select language (Italian, English, or Deutsch)" and
the other instance noted near line 353 so they read "Select language (Italian,
English, or German)" (and any other occurrences of "Deutsch") to keep
terminology consistent with the "German" entry used earlier.
In `@scripts/manual-upgrade.php`:
- Around line 350-352: The current check in scripts/manual-upgrade.php only logs
a warning when $targetVersion is not newer than $currentVersion; change this to
abort the process: replace the warning-only branch in the version_compare(...)
conditional to log an error (using $log or error_log) and then immediately stop
execution (e.g., throw an exception or call exit with non-zero status) so the
upgrade script cannot continue when $targetVersion <= $currentVersion.
- Around line 447-451: In the foreach over $statements as $stmtSql, don’t skip
the whole chunk just because it begins with a comment line; instead strip
leading SQL comment lines (lines beginning with "--") from $stmtSql, then trim
and run the emptiness check on the stripped version so any executable SQL after
initial comments is still executed. Update the logic around $stmtSql (the
trim/str_starts_with check) to first remove leading comment lines (but preserve
non-comment lines and inline comments) and only continue when the remaining
trimmed string is empty.
- Around line 275-291: The code currently injects the DB password into the
mysqldump argument list via '--password=' in the $args array (around usage of
$mysqldumpBin and $env), which can expose credentials; change this to create a
secure temporary credentials file (e.g., write a file containing [client] and
password=... from $env['DB_PASS']), set strict permissions (600), add the file
path to $args as '--defaults-extra-file=' . $tmpFile instead of
'--password=...', run the command (using escapeshellarg for $tmpFile in the
constructed $safeCmd), and ensure the temp file is securely deleted after
execution and on error before returning; keep the other args ($args, --host,
--user, --port, --socket, --single-transaction, --routines, --triggers,
$env['DB_NAME']) unchanged.
---
Duplicate comments:
In `@app/Controllers/UpdateController.php`:
- Around line 500-503: The code stores the GitHub PAT in plaintext via
Updater::saveGitHubToken, so change persistence to encrypt-at-rest: update
Updater::saveGitHubToken (or its caller) to encrypt the token using the app's
centralized encryption service/key before writing to the DB and mark the setting
as encrypted; then only decrypt inside the code path that constructs outbound
GitHub headers (e.g., Updater::getGitHubHeaders / Updater::buildAuthHeaders) so
Authorization headers are built from a decrypted value in memory, not persisted
in plaintext. Ensure you use the existing application encryption API/config (app
key or crypto service) and preserve backwards compatibility by
detecting/handling previously stored plaintext values where necessary.
In `@app/Support/Updater.php`:
- Around line 205-207: The GitHub token is being stored in plaintext via
assignments to $this->githubToken; change the write path (e.g., setGithubToken
or where $this->githubToken is assigned at the shown snippet) to encrypt the
token before persisting (use your app's encryption API, e.g., Laravel's
Crypt::encryptString) and change the read path (where the token is read back,
e.g., getGithubToken or the code at lines ~231-252) to decrypt it
(Crypt::decryptString) before use; ensure the stored property/DB column holds
the encrypted string, handle empty strings/null the same as today, and add
try/catch around decrypt to surface errors rather than returning raw ciphertext.
---
Nitpick comments:
In `@app/Views/admin/updates.php`:
- Around line 553-563: The token POST handlers that fetch '/admin/updates/token'
(the fetch block constructing body with csrf_token and github_token) and the
similar fetch at lines 595-605 must mirror startUpdate(): before calling
response.json() check response.headers.get('content-type') includes
'application/json'; if not, await response.text() and throw or surface a clear
error containing that text (or response.status) instead of calling .json(), so
CSRF/server HTML errors don't trigger a SyntaxError. Update both token-fetching
functions to perform this content-type guard and handle non-JSON responses as
startUpdate() does.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
README.mdapp/Controllers/UpdateController.phpapp/Routes/web.phpapp/Support/I18n.phpapp/Support/Updater.phpapp/Views/admin/updates.phpinstaller/classes/Installer.phpinstaller/database/data_de_DE.sqlinstaller/index.phpinstaller/steps/step0.phplocale/de_DE.jsonlocale/routes_de_DE.jsonscripts/manual-upgrade.php
…y, version guard - Updater: encrypt GitHub PAT at rest (AES-256-GCM, ENC: prefix, backward-compatible with plaintext) - manual-upgrade: use --defaults-extra-file for mysqldump instead of --password in process args - manual-upgrade: abort upgrade when target version <= current (was warn-only) - manual-upgrade: strip SQL comment lines instead of skipping whole statements - updates.php: add Content-Type pre-check before response.json() on token save/remove - README: consistent "German" instead of "Deutsch"
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
vendor/composer/installed.php (1)
1-290: Consider excludingvendor/from version control.This is a Composer auto-generated file that gets regenerated on every
composer install. Tracking thevendor/directory in Git is generally discouraged because:
- It bloats repository size
- Dependencies can be reliably reproduced from
composer.lock- It creates noise in diffs when unrelated changes bump internal references (like this commit hash update)
If deployment environments lack Composer access, consider using CI/CD to bundle dependencies in release artifacts instead.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/composer/installed.php` around lines 1 - 290, The repo is tracking the vendor/ directory (e.g., vendor/composer/installed.php); stop committing generated Composer dependencies by adding vendor/ to .gitignore, removing the tracked files with git rm -r --cached vendor (or equivalent), and committing that removal; ensure composer.json and composer.lock remain committed so dependencies can be reproduced and CI/CD or release artifacts include the vendor tree for deployments that need it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/Support/Updater.php`:
- Around line 221-238: The current token encryption block (in Updater.php)
silently returns the plaintext on failures (the early return when !$rawKey ||
$plain === '' and the returns inside the openssl failure branch and catch
block), which allows plaintext to be persisted; change this to fail closed: do
not return raw $plain on error—either throw a specific EncryptionException or
return a distinct failure value (e.g., null/false) so callers (e.g., the code
paths that persist tokens) will not store plaintext; update the three locations
that currently "return $plain" to instead propagate an error (throw new
\RuntimeException or return null) and ensure the successful path still returns
'ENC:' . base64_encode($iv . $tag . $ciphertext) only on successful encryption
(keep key derivation via hash(...) and iv/tag handling unchanged).
In `@scripts/manual-upgrade.php`:
- Line 539: The current code appends internal filesystem paths using
$e->getFile() and $e->getLine() to the user-visible $log, which leaks server
internals; update the handling where $log is populated (the exception catch that
uses $log[] and the exception variable $e) to remove file/line details from
output and instead append a generic, non-sensitive message (e.g., an anonymized
trace or "internal error during upgrade") to $log for the browser, and send the
full exception details to server-side logging via error_log or your existing
logger (call error_log with $e->__toString() or $e->getMessage() plus context)
so diagnostics remain available without exposing filesystem paths.
- Around line 212-214: After successful password authentication (the branch
where $_POST['password'] === UPGRADE_PASSWORD), rotate the session ID before
setting authenticated state to prevent session fixation: call
session_regenerate_id(true) and then set $_SESSION['upgrade_auth'] = true (or
perform the regenerate immediately after setting the flag if you prefer),
ensuring you reference the existing authentication check and session variable
names ($_POST['password'], UPGRADE_PASSWORD, $_SESSION['upgrade_auth']) so the
change is applied in the same conditional block.
- Around line 338-339: Check the boolean return of ZipArchive::extractTo when
extracting into $tempDir and abort on failure: after calling
$zip->extractTo($tempDir) (the $zip object in scripts/manual-upgrade.php) verify
the result is true, call $zip->close() and then log/throw an error and stop
further processing if it returned false so partial or failed extractions don't
continue; ensure any cleanup (removing $tempDir if created) runs on failure.
---
Nitpick comments:
In `@vendor/composer/installed.php`:
- Around line 1-290: The repo is tracking the vendor/ directory (e.g.,
vendor/composer/installed.php); stop committing generated Composer dependencies
by adding vendor/ to .gitignore, removing the tracked files with git rm -r
--cached vendor (or equivalent), and committing that removal; ensure
composer.json and composer.lock remain committed so dependencies can be
reproduced and CI/CD or release artifacts include the vendor tree for
deployments that need it.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
README.mdapp/Support/Updater.phpapp/Views/admin/updates.phpscripts/manual-upgrade.phpvendor/composer/installed.php
There was a problem hiding this comment.
♻️ Duplicate comments (4)
scripts/manual-upgrade.php (3)
338-339:⚠️ Potential issue | 🟠 MajorAbort when ZIP extraction fails.
Line 338 ignores
ZipArchive::extractTo()’s boolean return, so the script can proceed after a failed/partial extraction.💡 Suggested fix
- $zip->extractTo($tempDir); - $zip->close(); + if (!$zip->extractTo($tempDir)) { + $zip->close(); + throw new RuntimeException('Estrazione ZIP fallita'); + } + $zip->close();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/manual-upgrade.php` around lines 338 - 339, The code calls ZipArchive::extractTo via $zip->extractTo($tempDir) but ignores its boolean return value, so update the block around $zip->extractTo and $zip->close() to check the return; if extractTo returns false, close the archive (via $zip->close()), log or echo a clear error mentioning $tempDir and abort the script (exit with non-zero status or throw an exception) and perform any necessary cleanup of partial files; only proceed when extractTo returns true.
212-214:⚠️ Potential issue | 🟠 MajorRegenerate session ID on successful authentication.
Line 213 sets authenticated session state without rotating the session ID, leaving a session fixation window.
💡 Suggested fix
- } elseif ($_POST['password'] === UPGRADE_PASSWORD) { - $_SESSION['upgrade_auth'] = true; + } elseif ($_POST['password'] === UPGRADE_PASSWORD) { + session_regenerate_id(true); + $_SESSION['upgrade_auth'] = true; } else {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/manual-upgrade.php` around lines 212 - 214, Regenerate the session ID when authentication succeeds to prevent session fixation: after verifying the password (the branch where $_POST['password'] === UPGRADE_PASSWORD) call session_regenerate_id(true) and then set $_SESSION['upgrade_auth'] = true (ensure a session is started via session_start() earlier in the script). This ensures the authenticated state set by $_SESSION['upgrade_auth'] is tied to a fresh session ID.
539-539:⚠️ Potential issue | 🟠 MajorDo not expose filesystem internals in user-visible logs.
Line 539 leaks absolute file paths and source lines to the browser output. Keep detailed traces server-side only.
💡 Suggested fix
- $log[] = '[TRACE] ' . $e->getFile() . ':' . $e->getLine(); + $log[] = '[TRACE] Errore interno durante l\'upgrade.'; + error_log('[manual-upgrade] ' . $e->__toString());🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/manual-upgrade.php` at line 539, Remove exposure of filesystem paths and line numbers from the user-visible $log array: replace the line that appends "[TRACE] " . $e->getFile() . ':' . $e->getLine() to $log with a generic trace message (e.g., "[TRACE] exception in upgrade process") and send the detailed trace (file, line, stack) to a server-side log using error_log or the existing logger; locate the code around the $log array and exception variable $e in manual-upgrade.php and ensure only sanitized messages are added to $log while full exception details are recorded server-side.app/Support/Updater.php (1)
300-322:⚠️ Potential issue | 🟠 MajorMissing verification that encryption succeeded before persisting.
Lines 312-313 call
encryptValue()but don't verify the result is actually encrypted. If encryption fails silently (due to missing key or errors), the plaintext token will be persisted to the database.Add a guard to ensure only encrypted tokens are stored:
🔐 Proposed fix
$cat = 'updater'; $key = 'github_token'; $encrypted = $token !== '' ? $this->encryptValue($token) : ''; + if ($token !== '' && !str_starts_with($encrypted, 'ENC:')) { + throw new Exception(__('Impossibile cifrare il token GitHub: salvataggio annullato')); + } $stmt->bind_param('sss', $cat, $key, $encrypted);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Support/Updater.php` around lines 300 - 322, In saveGitHubToken, ensure encryptValue actually succeeded before writing to the DB: call $this->encryptValue($token), verify the returned $encrypted is non-empty and not equal to the raw $token (or otherwise indicates success), and if encryption failed throw an Exception (do not proceed to bind/execute the INSERT); only bind/execute and set $this->githubToken when encryption succeeded. Refer to saveGitHubToken and encryptValue for where to add the guard and the exception handling.
🧹 Nitpick comments (4)
vendor/composer/installed.php (1)
6-6: Removevendor/from version control; onlycomposer.lockis needed for reproducible builds.The
vendor/directory is not properly excluded from.gitignore(onlyvendor/**/.github/is excluded), and 2,402 vendor files are currently tracked by git. This bloats the repository and introduces unnecessary merge conflicts.Standard practice is to commit only
composer.lockand ignorevendor/. Runcomposer installto restore dependencies. If vendor files must be committed for deployment reasons, this is acceptable—but document the rationale.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/composer/installed.php` at line 6, The repo currently tracks vendor/ (e.g., files like vendor/composer/installed.php with 'reference' entries), which should be removed from version control; update .gitignore to include vendor/ (rather than only vendor/**/.github/), stop tracking existing vendor files (git rm -r --cached vendor) and commit that removal, ensure composer.lock is committed so dependencies are reproducible, and document the rationale only if you intentionally keep vendor/ committed for deployment.app/Controllers/UpdateController.php (1)
39-40: Remove unused$dataassignment inindex().
$datais not read before rendering and adds static-analysis noise.🧹 Proposed cleanup
ob_start(); - $data = compact('updateInfo', 'requirements', 'history', 'changelog', 'githubTokenMasked', 'hasGithubToken'); require __DIR__ . '/../Views/admin/updates.php'; $content = ob_get_clean();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Controllers/UpdateController.php` around lines 39 - 40, In the index() method remove the unused assignment to $data (the line using compact('updateInfo', 'requirements', 'history', 'changelog', 'githubTokenMasked', 'hasGithubToken')) since those variables are already available to the required view and $data is never used; simply delete that assignment to eliminate static-analysis noise while keeping the subsequent require __DIR__ . '/../Views/admin/updates.php' call intact.installer/classes/Installer.php (1)
258-270: Remove unreachable locale-map entries after normalization.
$normalizedLocaleis always lowercase with underscores, so canonical keys likeit_IT,en_US, andde_DEare never matched. Keeping only normalized keys reduces maintenance drift.♻️ Proposed cleanup
- $localeMap = [ - 'it' => 'it_IT', - 'it_it' => 'it_IT', - 'it_IT' => 'it_IT', - 'en' => 'en_US', - 'en_us' => 'en_US', - 'en_US' => 'en_US', - 'de' => 'de_DE', - 'de_de' => 'de_DE', - 'de_DE' => 'de_DE', - ]; + $localeMap = [ + 'it' => 'it_IT', + 'it_it' => 'it_IT', + 'en' => 'en_US', + 'en_us' => 'en_US', + 'de' => 'de_DE', + 'de_de' => 'de_DE', + ];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@installer/classes/Installer.php` around lines 258 - 270, The locale map contains unreachable mixed-case keys because $normalizedLocale is lowercase with underscores; update the $localeMap used by Installer:: where $normalizedLocale is computed so it only contains normalized keys (e.g. 'it', 'it_it', 'en', 'en_us', 'de', 'de_de') and remove entries like 'it_IT', 'en_US', 'de_DE'; keep the fallback assignment to $fullLocale = $localeMap[$normalizedLocale] ?? 'it_IT' unchanged so the default remains 'it_IT'.app/Views/admin/updates.php (1)
552-635: Deduplicate token API request handling.
saveGitHubToken()andremoveGitHubToken()repeat the same fetch headers, content-type check, and JSON parsing. A shared helper would reduce drift risk.♻️ Proposed refactor
+async function postGitHubToken(tokenValue) { + const response = await fetch(window.BASE_PATH + '/admin/updates/token', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Accept': 'application/json', + 'X-Requested-With': 'XMLHttpRequest' + }, + body: `csrf_token=${encodeURIComponent(csrfToken)}&github_token=${encodeURIComponent(tokenValue)}` + }); + + const ct = response.headers.get('content-type') || ''; + if (!ct.includes('application/json')) { + const text = await response.text(); + console.error('Server returned non-JSON response:', text.substring(0, 500)); + throw new Error(<?= json_encode(__("Il server ha restituito una risposta non valida. Controlla i log per dettagli."), JSON_HEX_TAG) ?>); + } + + return response.json(); +} @@ - const response = await fetch(window.BASE_PATH + '/admin/updates/token', { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Accept': 'application/json', - 'X-Requested-With': 'XMLHttpRequest' - }, - body: `csrf_token=${encodeURIComponent(csrfToken)}&github_token=${encodeURIComponent(token)}` - }); - - const ct = response.headers.get('content-type') || ''; - if (!ct.includes('application/json')) { - const text = await response.text(); - console.error('Server returned non-JSON response:', text.substring(0, 500)); - throw new Error(<?= json_encode(__("Il server ha restituito una risposta non valida. Controlla i log per dettagli."), JSON_HEX_TAG) ?>); - } - - const data = await response.json(); + const data = await postGitHubToken(token); @@ - const response = await fetch(window.BASE_PATH + '/admin/updates/token', { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Accept': 'application/json', - 'X-Requested-With': 'XMLHttpRequest' - }, - body: `csrf_token=${encodeURIComponent(csrfToken)}&github_token=` - }); - - const ct = response.headers.get('content-type') || ''; - if (!ct.includes('application/json')) { - const text = await response.text(); - console.error('Server returned non-JSON response:', text.substring(0, 500)); - throw new Error(<?= json_encode(__("Il server ha restituito una risposta non valida. Controlla i log per dettagli."), JSON_HEX_TAG) ?>); - } - - const data = await response.json(); + const data = await postGitHubToken('');🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Views/admin/updates.php` around lines 552 - 635, Both saveGitHubToken() and removeGitHubToken() duplicate the same fetch setup, content-type checking and JSON parsing; extract that common behavior into a helper (e.g., postTokenRequest or postJsonToTokenEndpoint) that accepts the github_token value (and csrfToken) and returns the parsed JSON or throws on non-JSON response; update saveGitHubToken() and removeGitHubToken() to call this helper and keep only the UI/Swal handling; ensure the helper replicates the existing headers ('Content-Type','Accept','X-Requested-With'), content-type validation using response.headers.get('content-type'), console.error of non-JSON body, and throws the same localized Error message so behavior remains identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@app/Support/Updater.php`:
- Around line 300-322: In saveGitHubToken, ensure encryptValue actually
succeeded before writing to the DB: call $this->encryptValue($token), verify the
returned $encrypted is non-empty and not equal to the raw $token (or otherwise
indicates success), and if encryption failed throw an Exception (do not proceed
to bind/execute the INSERT); only bind/execute and set $this->githubToken when
encryption succeeded. Refer to saveGitHubToken and encryptValue for where to add
the guard and the exception handling.
In `@scripts/manual-upgrade.php`:
- Around line 338-339: The code calls ZipArchive::extractTo via
$zip->extractTo($tempDir) but ignores its boolean return value, so update the
block around $zip->extractTo and $zip->close() to check the return; if extractTo
returns false, close the archive (via $zip->close()), log or echo a clear error
mentioning $tempDir and abort the script (exit with non-zero status or throw an
exception) and perform any necessary cleanup of partial files; only proceed when
extractTo returns true.
- Around line 212-214: Regenerate the session ID when authentication succeeds to
prevent session fixation: after verifying the password (the branch where
$_POST['password'] === UPGRADE_PASSWORD) call session_regenerate_id(true) and
then set $_SESSION['upgrade_auth'] = true (ensure a session is started via
session_start() earlier in the script). This ensures the authenticated state set
by $_SESSION['upgrade_auth'] is tied to a fresh session ID.
- Line 539: Remove exposure of filesystem paths and line numbers from the
user-visible $log array: replace the line that appends "[TRACE] " .
$e->getFile() . ':' . $e->getLine() to $log with a generic trace message (e.g.,
"[TRACE] exception in upgrade process") and send the detailed trace (file, line,
stack) to a server-side log using error_log or the existing logger; locate the
code around the $log array and exception variable $e in manual-upgrade.php and
ensure only sanitized messages are added to $log while full exception details
are recorded server-side.
---
Nitpick comments:
In `@app/Controllers/UpdateController.php`:
- Around line 39-40: In the index() method remove the unused assignment to $data
(the line using compact('updateInfo', 'requirements', 'history', 'changelog',
'githubTokenMasked', 'hasGithubToken')) since those variables are already
available to the required view and $data is never used; simply delete that
assignment to eliminate static-analysis noise while keeping the subsequent
require __DIR__ . '/../Views/admin/updates.php' call intact.
In `@app/Views/admin/updates.php`:
- Around line 552-635: Both saveGitHubToken() and removeGitHubToken() duplicate
the same fetch setup, content-type checking and JSON parsing; extract that
common behavior into a helper (e.g., postTokenRequest or
postJsonToTokenEndpoint) that accepts the github_token value (and csrfToken) and
returns the parsed JSON or throws on non-JSON response; update saveGitHubToken()
and removeGitHubToken() to call this helper and keep only the UI/Swal handling;
ensure the helper replicates the existing headers
('Content-Type','Accept','X-Requested-With'), content-type validation using
response.headers.get('content-type'), console.error of non-JSON body, and throws
the same localized Error message so behavior remains identical.
In `@installer/classes/Installer.php`:
- Around line 258-270: The locale map contains unreachable mixed-case keys
because $normalizedLocale is lowercase with underscores; update the $localeMap
used by Installer:: where $normalizedLocale is computed so it only contains
normalized keys (e.g. 'it', 'it_it', 'en', 'en_us', 'de', 'de_de') and remove
entries like 'it_IT', 'en_US', 'de_DE'; keep the fallback assignment to
$fullLocale = $localeMap[$normalizedLocale] ?? 'it_IT' unchanged so the default
remains 'it_IT'.
In `@vendor/composer/installed.php`:
- Line 6: The repo currently tracks vendor/ (e.g., files like
vendor/composer/installed.php with 'reference' entries), which should be removed
from version control; update .gitignore to include vendor/ (rather than only
vendor/**/.github/), stop tracking existing vendor files (git rm -r --cached
vendor) and commit that removal, ensure composer.lock is committed so
dependencies are reproducible, and document the rationale only if you
intentionally keep vendor/ committed for deployment.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
README.mdapp/Controllers/UpdateController.phpapp/Routes/web.phpapp/Support/I18n.phpapp/Support/Updater.phpapp/Views/admin/updates.phpinstaller/classes/Installer.phpinstaller/database/data_de_DE.sqlinstaller/index.phpinstaller/steps/step0.phplocale/de_DE.jsonlocale/routes_de_DE.jsonscripts/manual-upgrade.phpvendor/composer/installed.php
…ation, cleanup - Updater: encryptValue throws on failure instead of returning plaintext (fail-closed) - Updater: saveGitHubToken guards that ENC: prefix is present before persisting - manual-upgrade: session_regenerate_id(true) after successful authentication - manual-upgrade: check ZipArchive::extractTo() return value, abort on failure - manual-upgrade: move filesystem trace to error_log, keep user log generic - updates.php: extract postTokenRequest() helper to deduplicate save/remove fetch logic - UpdateController: remove unused $data compact() assignment - Installer: remove unreachable mixed-case locale map entries
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
installer/database/data_de_DE.sql (1)
4-4:⚠️ Potential issue | 🟠 Major
FOREIGN_KEY_CHECKScan remain disabled if execution aborts early.Line 4 disables FK checks and they are only restored at Line 347. Any SQL error before the end can leave the installer DB session in an unsafe state for subsequent operations.
🛠️ Safer baseline restore in-script
-SET FOREIGN_KEY_CHECKS=0; +SET `@OLD_FOREIGN_KEY_CHECKS` := @@FOREIGN_KEY_CHECKS; +SET FOREIGN_KEY_CHECKS=0; ... -SET FOREIGN_KEY_CHECKS=1; +SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;Please also ensure the installer SQL executor restores FK checks in a
finally-style path on exceptions/interrupted execution.Also applies to: 347-347
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@installer/database/data_de_DE.sql` at line 4, The script currently disables FOREIGN_KEY_CHECKS and only restores it much later; update the SQL and the installer SQL executor so FOREIGN_KEY_CHECKS is always re-enabled on error/interrupt: in the SQL bundle ensure you include a matching SET FOREIGN_KEY_CHECKS=1 close to any transactional/critical section boundaries and in the installer SQL execution code wrap execution in a try/finally (or equivalent) that always runs SET FOREIGN_KEY_CHECKS=1 (or issues the server command to restore FK checks) in the finally block; reference the FOREIGN_KEY_CHECKS toggle and the installer SQL executor function to implement the unconditional restore path.installer/index.php (1)
36-40:⚠️ Potential issue | 🟡 MinorNormalize locale before both translation-file and
<html lang>mapping.Line [36] and Line [347] still match raw locale values; variants like
de-DE/en-uscan miss expected mappings and fall back incorrectly. Use a shared normalizer before both decisions.♻️ Proposed refactor
+function normalizeInstallerLocale(string $locale): string { + $locale = str_replace('-', '_', strtolower($locale)); + return match($locale) { + 'en', 'en_us' => 'en_US', + 'de', 'de_de' => 'de_DE', + default => 'it_IT', + }; +} + // Simple translation function for installer function __(string $key, mixed ...$args): string { @@ - $localeCode = match($locale) { - 'en', 'en_US' => 'en_US', - 'de', 'de_DE' => 'de_DE', - default => $locale, - }; + $localeCode = normalizeInstallerLocale((string)$locale); $translationFile = dirname(__DIR__) . '/locale/' . $localeCode . '.json'; @@ - $lang = $_SESSION['app_locale'] ?? 'it'; - $htmlLang = match($lang) { - 'en', 'en_US' => 'en', - 'de', 'de_DE' => 'de', - default => 'it', - }; + $localeCode = normalizeInstallerLocale((string)($_SESSION['app_locale'] ?? 'it')); + $htmlLang = match($localeCode) { + 'en_US' => 'en', + 'de_DE' => 'de', + default => 'it', + };Also applies to: 347-351
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@installer/index.php` around lines 36 - 40, Create and call a single locale normalizer (e.g., normalizeLocale) that canonicalizes input $locale to a consistent format (lowercase, convert '-' to '_', map common variants like 'en_us'/'en-us' to 'en_US' and 'de_de'/'de-de' to 'de_DE') and use its output everywhere you currently use $locale or $localeCode; replace the inline match on $locale in the $localeCode assignment and the separate mapping used for the HTML lang (the second match block around lines 347-351) to operate on normalizeLocale($locale) so both translation-file selection and the <html lang> mapping use the same normalized value.app/Support/Updater.php (1)
306-323:⚠️ Potential issue | 🟠 MajorReject control characters in token input before persistence.
Line [308] only trims boundaries. A token containing control characters (especially CR/LF) can corrupt the
Authorizationheader assembled at Line [297]. Validate and reject control chars before encryption/save.🔧 Proposed fix
public function saveGitHubToken(string $token): void { $token = trim($token); + if ($token !== '' && preg_match('/[[:cntrl:]]/u', $token)) { + throw new Exception(__('Token GitHub non valido')); + } $stmt = $this->db->prepare( 'INSERT INTO system_settings (category, setting_key, setting_value) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)' );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Support/Updater.php` around lines 306 - 323, In saveGitHubToken, reject any token containing control characters (CR, LF, or other ASCII control bytes) before calling encryptValue and before binding/persisting; after trim(), validate the token (e.g., detect \r or \n or general control chars) and throw an Exception with a clear message if found, so the Authorization header assembler that consumes this token never receives embedded control characters; perform this check on the trimmed value and only proceed to encryptValue, prepare/bind_param and execute when the token passes validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@locale/routes_de_DE.json`:
- Line 15: The "events" key in locale/routes_de_DE.json currently maps to
"/veranstaltungen", which doesn't match the registered endpoints /events and
/events/{slug} and will 404; fix by either updating the "events" translation
value to "/events" (so the "events" key resolves to existing routes) or
alternatively add localized routes for "/veranstaltungen" and
"/veranstaltungen/{slug}" in your routing (app/Routes/web.php) so the "events"
key can remain "/veranstaltungen".
---
Duplicate comments:
In `@app/Support/Updater.php`:
- Around line 306-323: In saveGitHubToken, reject any token containing control
characters (CR, LF, or other ASCII control bytes) before calling encryptValue
and before binding/persisting; after trim(), validate the token (e.g., detect \r
or \n or general control chars) and throw an Exception with a clear message if
found, so the Authorization header assembler that consumes this token never
receives embedded control characters; perform this check on the trimmed value
and only proceed to encryptValue, prepare/bind_param and execute when the token
passes validation.
In `@installer/database/data_de_DE.sql`:
- Line 4: The script currently disables FOREIGN_KEY_CHECKS and only restores it
much later; update the SQL and the installer SQL executor so FOREIGN_KEY_CHECKS
is always re-enabled on error/interrupt: in the SQL bundle ensure you include a
matching SET FOREIGN_KEY_CHECKS=1 close to any transactional/critical section
boundaries and in the installer SQL execution code wrap execution in a
try/finally (or equivalent) that always runs SET FOREIGN_KEY_CHECKS=1 (or issues
the server command to restore FK checks) in the finally block; reference the
FOREIGN_KEY_CHECKS toggle and the installer SQL executor function to implement
the unconditional restore path.
In `@installer/index.php`:
- Around line 36-40: Create and call a single locale normalizer (e.g.,
normalizeLocale) that canonicalizes input $locale to a consistent format
(lowercase, convert '-' to '_', map common variants like 'en_us'/'en-us' to
'en_US' and 'de_de'/'de-de' to 'de_DE') and use its output everywhere you
currently use $locale or $localeCode; replace the inline match on $locale in the
$localeCode assignment and the separate mapping used for the HTML lang (the
second match block around lines 347-351) to operate on normalizeLocale($locale)
so both translation-file selection and the <html lang> mapping use the same
normalized value.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
README.mdapp/Controllers/UpdateController.phpapp/Routes/web.phpapp/Support/I18n.phpapp/Support/Updater.phpapp/Views/admin/updates.phpinstaller/classes/Installer.phpinstaller/database/data_de_DE.sqlinstaller/index.phpinstaller/steps/step0.phplocale/de_DE.jsonlocale/routes_de_DE.jsonscripts/manual-upgrade.phpvendor/composer/installed.php
…rmalizer - web.php: register events routes via RouteTranslator loop (fixes /veranstaltungen 404) - Updater: reject control characters in GitHub token before persist - Installer: try/finally to always restore FOREIGN_KEY_CHECKS on data import - installer/index.php: shared normalizeInstallerLocale() for translation + HTML lang
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (5)
scripts/manual-upgrade.php (1)
351-358:⚠️ Potential issue | 🟠 MajorFail fast if mysqldump credentials file cannot be written.
Line 351 currently ignores
file_put_contents()failure; backup can proceed with a bad/empty defaults file and weaken recovery guarantees.🧯 Suggested patch
- file_put_contents($defaultsFile, + $written = file_put_contents($defaultsFile, "[client]\n" . "host=" . ($env['DB_HOST'] ?? 'localhost') . "\n" . "user=" . ($env['DB_USER'] ?? '') . "\n" . "password=" . ($env['DB_PASS'] ?? '') . "\n" . "port=" . (int) ($env['DB_PORT'] ?? 3306) . "\n" ); + if ($written === false || $written === 0) { + `@unlink`($defaultsFile); + throw new RuntimeException('Impossibile scrivere il file temporaneo credenziali mysqldump'); + } `@chmod`($defaultsFile, 0600);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/manual-upgrade.php` around lines 351 - 358, The code currently calls file_put_contents($defaultsFile, ...) and then `@chmod`($defaultsFile, 0600) without checking for failure; update the logic in the manual-upgrade script to check the return value of file_put_contents for false and handle failure by logging an error (including $defaultsFile and the PHP error message), preventing further backup/upgrade steps (exit or throw), and only call chmod when file_put_contents succeeded; reference the $defaultsFile variable, the file_put_contents() call and the `@chmod`() call when making this change.installer/database/data_de_DE.sql (1)
290-291:⚠️ Potential issue | 🟡 MinorAvoid root-absolute links in seeded content.
Line 290, Line 291, Line 322, and Line 332 use
/...paths. In subfolder installs these can bypass the app base path and break navigation.🔧 Suggested patch
-... href=\"/admin/cms/about-us\" ... +... href=\"admin/cms/about-us\" ... -... href=\"/admin/cms/privacy-policy\" ... +... href=\"admin/cms/privacy-policy\" ... -('hero', ..., 'Katalog entdecken', '/katalog', ...) +('hero', ..., 'Katalog entdecken', 'katalog', ...) -('cta', ..., 'Jetzt registrieren', '/registrieren', ...) +('cta', ..., 'Jetzt registrieren', 'registrieren', ...)Also applies to: 322-323, 332-332
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@installer/database/data_de_DE.sql` around lines 290 - 291, The seeded CMS page records for slugs 'about-us' and 'privacy-policy' include root-absolute links (e.g. '/admin/cms/about-us' and '/admin/cms/privacy-policy') which can break installs in a subfolder; update those link strings in the INSERTs to use either a relative path ('admin/cms/about-us', etc.) or a configurable base-path placeholder (e.g. '{{APP_BASE_PATH}}/admin/cms/about-us') and apply the same change to every occurrence in the 'about-us' and 'privacy-policy' content entries (and the other duplicated occurrences called out) so seeded content respects the app base path.app/Support/Updater.php (2)
739-780:⚠️ Potential issue | 🟠 MajorHandle auth failures in
getAllReleases()before treating payload as release list.With an invalid/revoked token at Line 739 or Line 757, GitHub can return an error JSON object instead of a release array. This method currently returns that object, and downstream
getChangelog()expects list items withtag_name, which can break at runtime.🔧 Proposed fix
- if ($curlResult !== false && $httpCode >= 200 && $httpCode < 400) { + if ($curlResult !== false && $httpCode >= 200 && $httpCode < 400) { $response = $curlResult; + } elseif ($httpCode === 401 || $httpCode === 403) { + // retry once without token + $headers = [ + 'User-Agent: Pinakes-Updater/1.0', + 'Accept: application/vnd.github.v3+json', + ]; + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + $retryResult = curl_exec($ch); + $retryCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + if ($retryResult !== false && $retryCode >= 200 && $retryCode < 400) { + $response = $retryResult; + } } @@ - if (!is_array($releases)) { + if (!is_array($releases) || !array_is_list($releases)) { $this->debugLog('ERROR', 'Risposta releases non valida', [ 'json_error' => json_last_error_msg() ]); return []; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Support/Updater.php` around lines 739 - 780, In getAllReleases(), don't treat any JSON payload as a releases list—check for auth/errors before returning: if an HTTP status (from $httpCode when curl used) is >=400 (especially 401/403) log and return []; after json_decode($response, true) detect an error object (assoc array with keys like "message", "error" or "documentation_url") or non-indexed array and in that case debugLog the GitHub error details (include json_last_error_msg() and the decoded message) and return []; ensure callers like getChangelog() only receive a numeric-indexed array of releases by validating $releases is an indexed array before using tag_name.
877-903:⚠️ Potential issue | 🟠 MajorAvoid blocking update ZIP download when PAT is invalid.
Line 877 and Line 913 always attach Authorization. If the saved token is revoked/invalid, both download paths can fail with 401 and the update aborts, even when anonymous download would work.
🔧 Proposed fix
- if ($curlErrno !== 0 || $httpCode >= 400) { + if ($curlErrno !== 0 || $httpCode >= 400) { + if (in_array($httpCode, [401, 403], true) && $this->githubToken !== '') { + $this->debugLog('WARNING', 'Retry download senza token', ['http_code' => $httpCode]); + $headers = [ + 'User-Agent: Pinakes-Updater/1.0', + 'Accept: application/octet-stream', + ]; + $ch = curl_init($downloadUrl); + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 300, + CURLOPT_CONNECTTIMEOUT => 30, + CURLOPT_USERAGENT => 'Pinakes-Updater/1.0', + CURLOPT_HTTPHEADER => $headers, + CURLOPT_SSL_VERIFYPEER => true, + ]); + $retryContent = curl_exec($ch); + $retryInfo = curl_getinfo($ch); + curl_close($ch); + if ($retryContent !== false && (($retryInfo['http_code'] ?? 0) < 400)) { + $fileContent = $retryContent; + } + } $this->debugLog('WARNING', 'cURL fallito, tentativo con file_get_contents', [ 'error' => $curlError, 'http_code' => $httpCode ]); - $fileContent = false; + if ($fileContent === false) { + $fileContent = false; + } }Also applies to: 913-942
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Support/Updater.php` around lines 877 - 903, The download always attaches Authorization via getGitHubHeaders('application/octet-stream') which causes both the cURL and file_get_contents paths to fail when the saved PAT is revoked; change the download flow so that if the request returns HTTP 401/403 (or curl errno indicating auth failure) you immediately retry the download without the Authorization header (i.e. call a header helper that omits the token or pass a flag to getGitHubHeaders to build anonymous headers) for both the cURL branch around the curl_exec/curl_getinfo logic and the fallback file_get_contents path, and only abort if the anonymous retry also fails.public/assets/main.css (1)
4662-4665:⚠️ Potential issue | 🟡 MinorFix unresolved Stylelint declaration spacing in generated utility blocks.
Line 4664 still violates
declaration-empty-line-before, and the same pattern repeats across the newly added utility blocks (for example Line 4688, Line 4855, Line 5490, Line 7050). This remains CI-blocking unless regenerated/auto-fixed.🧩 Example fix pattern
.border-amber-300 { --tw-border-opacity: 1; + border-color: rgb(252 211 77 / var(--tw-border-opacity, 1)); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@public/assets/main.css` around lines 4662 - 4665, The generated utility blocks (e.g., the .border-amber-300 block) violate the Stylelint rule declaration-empty-line-before because there is no empty line before the first declaration; update each generated utility class (for example .border-amber-300 and the other similar blocks around the noted lines) to insert a single blank line before the first declaration (so the line before "--tw-border-opacity: 1;" is empty) and apply the same spacing fix across all newly added utility blocks (or regenerate/auto-fix the CSS) so the declaration-empty-line-before rule is satisfied consistently.
🧹 Nitpick comments (5)
app/Views/libri/partials/book_form.php (1)
2245-2248: Minor redundancy in sync event listeners (acceptable).The
syncHiddencalls are already present within the change handlers above (lines 2194, 2202, 2229). These additionaladdEventListenercalls provide a safety net for any future code paths that might modify select values without going through the main handlers. This defensive approach is fine, though it does meansyncHidden()may execute twice per change event.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Views/libri/partials/book_form.php` around lines 2245 - 2248, Duplicate change listeners on genereSelect and sottogenereSelect cause syncHidden to run twice; remove the redundant addEventListener calls (the lines attaching 'change' to genereSelect and sottogenereSelect that call syncHidden) so only the existing handlers (which already call syncHidden in their change callbacks) remain, or alternatively consolidate by replacing the duplicates with a single shared handler registration to avoid double execution; locate references to genereSelect, sottogenereSelect, and syncHidden to make the change.locale/routes_de_DE.json (1)
1-32: Add an automated route-key parity check across locale route JSON files.This file looks good, but a CI check comparing key sets (e.g., against the base locale) would prevent future route-map drift and silent 404 regressions.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locale/routes_de_DE.json` around lines 1 - 32, Add a CI parity check that ensures the route key set in locale/routes_de_DE.json (e.g., keys like "login", "logout", "register", "verify_email", "api_catalog", "language_switch") exactly matches the base locale route file; implement a small script (e.g., routes_parity_check) that loads each locale JSON, computes set differences against the base locale (missing and extra keys), prints human-readable diffs and exits non-zero on mismatch, and wire that script into the CI pipeline to run on PRs so any future drift in route keys fails the build.app/Views/frontend/events.php (1)
32-38: Extract shared localized date/time formatting into a helper.This logic is now duplicated across multiple views (
app/Views/frontend/event-detail.php,app/Views/events/index.php, and this file). Centralizing it will reduce drift and keep locale fallback behavior consistent.Also applies to: 59-63, 65-78, 80-93
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Views/frontend/events.php` around lines 32 - 38, Extract the duplicated IntlDateFormatter setup into a single helper function (e.g., getLocalizedFormatters($locale)) that returns an array or object with dateFormatter and timeFormatter (falling back to null when class_exists('IntlDateFormatter') is false); replace the repeated blocks in the views (where $dateFormatter, $timeFormatter and $locale are used) with a call to getLocalizedFormatters($locale) and use the returned dateFormatter/timeFormatter values, ensuring the helper captures the LONG/SHORT and NONE constants exactly as in the original code so behavior and locale fallback remain unchanged.scripts/manual-upgrade.php (1)
260-269: Add basic authentication throttling to the upgrade login.This endpoint guards a high-impact operation; add attempt limits/cooldown (session + IP based) to reduce brute-force exposure.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/manual-upgrade.php` around lines 260 - 269, The POST password check needs rate-limiting: implement per-session and per-IP attempt tracking and cooldown around the existing check that uses UPGRADE_PASSWORD and sets $_SESSION['upgrade_auth']; on each failed attempt increment counters (e.g. $_SESSION['upgrade_attempts'] and $_SESSION['upgrade_last_attempt']) and persist per-IP attempts (e.g. simple flatfile or APCu keyed by $_SERVER['REMOTE_ADDR']) and if limits are exceeded return an $error with remaining cooldown; on successful hash_equals reset the session counters and regenerate id as you already do; ensure timestamps are stored to enforce a cooldown window and that counters decay after the window to avoid permanent lockout.app/Routes/web.php (1)
257-282: Consider centralizing hybrid profile-update aliases in a map.The locale
if/elseifbranch for hybrid aliases will keep growing as more locales are added. A map + shared handler is easier to maintain and less error-prone.♻️ Suggested refactor
- $profileUpdateRoute = RouteTranslator::getRouteForLocale('profile_update', $locale); - $registerRouteIfUnique('POST', $profileUpdateRoute, function ($request, $response) use ($app) { + $profileUpdateRoute = RouteTranslator::getRouteForLocale('profile_update', $locale); + $profileUpdateHandler = function ($request, $response) use ($app) { $db = $app->getContainer()->get('db'); $controller = new ProfileController(); return $controller->update($request, $response, $db); - }, [new CsrfMiddleware(), new AuthMiddleware(['admin', 'staff', 'standard', 'premium'])]); + }; + $registerRouteIfUnique('POST', $profileUpdateRoute, $profileUpdateHandler, [new CsrfMiddleware(), new AuthMiddleware(['admin', 'staff', 'standard', 'premium'])]); @@ - if ($locale === 'it_IT') { - // Italian base + English action - $registerRouteIfUnique('POST', '/profilo/update', function ($request, $response) use ($app) { - $db = $app->getContainer()->get('db'); - $controller = new ProfileController(); - return $controller->update($request, $response, $db); - }, [new CsrfMiddleware(), new AuthMiddleware(['admin', 'staff', 'standard', 'premium'])]); - } elseif ($locale === 'en_US') { - // English base + Italian action - $registerRouteIfUnique('POST', '/profile/aggiorna', function ($request, $response) use ($app) { - $db = $app->getContainer()->get('db'); - $controller = new ProfileController(); - return $controller->update($request, $response, $db); - }, [new CsrfMiddleware(), new AuthMiddleware(['admin', 'staff', 'standard', 'premium'])]); - } elseif ($locale === 'de_DE') { - // German base + English/Italian action - $registerRouteIfUnique('POST', '/profil/update', function ($request, $response) use ($app) { - $db = $app->getContainer()->get('db'); - $controller = new ProfileController(); - return $controller->update($request, $response, $db); - }, [new CsrfMiddleware(), new AuthMiddleware(['admin', 'staff', 'standard', 'premium'])]); - $registerRouteIfUnique('POST', '/profil/aggiorna', function ($request, $response) use ($app) { - $db = $app->getContainer()->get('db'); - $controller = new ProfileController(); - return $controller->update($request, $response, $db); - }, [new CsrfMiddleware(), new AuthMiddleware(['admin', 'staff', 'standard', 'premium'])]); - } + $profileUpdateAliases = [ + 'it_IT' => ['/profilo/update'], + 'en_US' => ['/profile/aggiorna'], + 'de_DE' => ['/profil/update', '/profil/aggiorna'], + ]; + foreach ($profileUpdateAliases[$locale] ?? [] as $aliasRoute) { + $registerRouteIfUnique('POST', $aliasRoute, $profileUpdateHandler, [new CsrfMiddleware(), new AuthMiddleware(['admin', 'staff', 'standard', 'premium'])]); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Routes/web.php` around lines 257 - 282, Replace the repeated locale-specific route blocks with a centralized map of hybrid aliases and a single shared handler: build an associative array keyed by $locale (or a global list) mapping to arrays of route paths (e.g. '/profilo/update','/profile/aggiorna','/profil/update','/profil/aggiorna'), then iterate over that map and call $registerRouteIfUnique for each path using one closure that retrieves $db from $app and invokes (new ProfileController())->update($request,$response,$db) with the same middleware stack (CsrfMiddleware and AuthMiddleware(['admin','staff','standard','premium'])); keep references to the existing symbols $registerRouteIfUnique, ProfileController::update, CsrfMiddleware, AuthMiddleware and $locale so the behavior and authorization remain identical while removing duplicated blocks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.htaccess:
- Around line 18-20: The manual-upgrade.php script currently relies on a
hardcoded default password 'pinakes2026' (commented) which must be changed
manually; update scripts/manual-upgrade.php to prevent accidental production use
by (a) loading the upgrade password from an environment variable (e.g.,
UPGRADE_PASSWORD) instead of a hardcoded string and (b) adding an explicit
runtime check in the authentication logic that aborts/returns an error if the
configured password equals the known default 'pinakes2026' or is empty—locate
the password variable/constant and the authentication block in
manual-upgrade.php and implement the env lookup plus the guard that fails fast
when the default is detected.
In `@app/Models/BookRepository.php`:
- Around line 348-351: The validation for anno_pubblicazione currently uses
filter_var with ['options' => ['min_range' => 1]] which rejects negative (BCE)
years; update the validation where anno_pubblicazione is parsed (the block using
hasColumn('anno_pubblicazione'), the filter_var call, and the subsequent
addField('anno_pubblicazione', 'i', ...)) to accept signed integers — either
remove the min_range option or set options to the DB integer bounds (e.g.
min_range => -2147483648, max_range => 2147483647) so negative years are
allowed, and preserve the existing behavior of passing null to addField when
validation fails.
In `@installer/classes/Installer.php`:
- Around line 481-483: Move the calls to $pdo->beginTransaction() and
$pdo->exec('SET FOREIGN_KEY_CHECKS=0') inside the guarded try block so failures
during transaction start or FK disable are covered by the existing
catch/rollback/finally logic; locate the block that currently calls
$pdo->beginTransaction() and $pdo->exec('SET FOREIGN_KEY_CHECKS=0') immediately
before the try, remove them from there, and place them as the first statements
inside the try (keeping the existing catch/rollback/finally that references
rollback()/commit()/exec('SET FOREIGN_KEY_CHECKS=1')).
---
Duplicate comments:
In `@app/Support/Updater.php`:
- Around line 739-780: In getAllReleases(), don't treat any JSON payload as a
releases list—check for auth/errors before returning: if an HTTP status (from
$httpCode when curl used) is >=400 (especially 401/403) log and return []; after
json_decode($response, true) detect an error object (assoc array with keys like
"message", "error" or "documentation_url") or non-indexed array and in that case
debugLog the GitHub error details (include json_last_error_msg() and the decoded
message) and return []; ensure callers like getChangelog() only receive a
numeric-indexed array of releases by validating $releases is an indexed array
before using tag_name.
- Around line 877-903: The download always attaches Authorization via
getGitHubHeaders('application/octet-stream') which causes both the cURL and
file_get_contents paths to fail when the saved PAT is revoked; change the
download flow so that if the request returns HTTP 401/403 (or curl errno
indicating auth failure) you immediately retry the download without the
Authorization header (i.e. call a header helper that omits the token or pass a
flag to getGitHubHeaders to build anonymous headers) for both the cURL branch
around the curl_exec/curl_getinfo logic and the fallback file_get_contents path,
and only abort if the anonymous retry also fails.
In `@installer/database/data_de_DE.sql`:
- Around line 290-291: The seeded CMS page records for slugs 'about-us' and
'privacy-policy' include root-absolute links (e.g. '/admin/cms/about-us' and
'/admin/cms/privacy-policy') which can break installs in a subfolder; update
those link strings in the INSERTs to use either a relative path
('admin/cms/about-us', etc.) or a configurable base-path placeholder (e.g.
'{{APP_BASE_PATH}}/admin/cms/about-us') and apply the same change to every
occurrence in the 'about-us' and 'privacy-policy' content entries (and the other
duplicated occurrences called out) so seeded content respects the app base path.
In `@public/assets/main.css`:
- Around line 4662-4665: The generated utility blocks (e.g., the
.border-amber-300 block) violate the Stylelint rule
declaration-empty-line-before because there is no empty line before the first
declaration; update each generated utility class (for example .border-amber-300
and the other similar blocks around the noted lines) to insert a single blank
line before the first declaration (so the line before "--tw-border-opacity: 1;"
is empty) and apply the same spacing fix across all newly added utility blocks
(or regenerate/auto-fix the CSS) so the declaration-empty-line-before rule is
satisfied consistently.
In `@scripts/manual-upgrade.php`:
- Around line 351-358: The code currently calls file_put_contents($defaultsFile,
...) and then `@chmod`($defaultsFile, 0600) without checking for failure; update
the logic in the manual-upgrade script to check the return value of
file_put_contents for false and handle failure by logging an error (including
$defaultsFile and the PHP error message), preventing further backup/upgrade
steps (exit or throw), and only call chmod when file_put_contents succeeded;
reference the $defaultsFile variable, the file_put_contents() call and the
`@chmod`() call when making this change.
---
Nitpick comments:
In `@app/Routes/web.php`:
- Around line 257-282: Replace the repeated locale-specific route blocks with a
centralized map of hybrid aliases and a single shared handler: build an
associative array keyed by $locale (or a global list) mapping to arrays of route
paths (e.g.
'/profilo/update','/profile/aggiorna','/profil/update','/profil/aggiorna'), then
iterate over that map and call $registerRouteIfUnique for each path using one
closure that retrieves $db from $app and invokes (new
ProfileController())->update($request,$response,$db) with the same middleware
stack (CsrfMiddleware and
AuthMiddleware(['admin','staff','standard','premium'])); keep references to the
existing symbols $registerRouteIfUnique, ProfileController::update,
CsrfMiddleware, AuthMiddleware and $locale so the behavior and authorization
remain identical while removing duplicated blocks.
In `@app/Views/frontend/events.php`:
- Around line 32-38: Extract the duplicated IntlDateFormatter setup into a
single helper function (e.g., getLocalizedFormatters($locale)) that returns an
array or object with dateFormatter and timeFormatter (falling back to null when
class_exists('IntlDateFormatter') is false); replace the repeated blocks in the
views (where $dateFormatter, $timeFormatter and $locale are used) with a call to
getLocalizedFormatters($locale) and use the returned dateFormatter/timeFormatter
values, ensuring the helper captures the LONG/SHORT and NONE constants exactly
as in the original code so behavior and locale fallback remain unchanged.
In `@app/Views/libri/partials/book_form.php`:
- Around line 2245-2248: Duplicate change listeners on genereSelect and
sottogenereSelect cause syncHidden to run twice; remove the redundant
addEventListener calls (the lines attaching 'change' to genereSelect and
sottogenereSelect that call syncHidden) so only the existing handlers (which
already call syncHidden in their change callbacks) remain, or alternatively
consolidate by replacing the duplicates with a single shared handler
registration to avoid double execution; locate references to genereSelect,
sottogenereSelect, and syncHidden to make the change.
In `@locale/routes_de_DE.json`:
- Around line 1-32: Add a CI parity check that ensures the route key set in
locale/routes_de_DE.json (e.g., keys like "login", "logout", "register",
"verify_email", "api_catalog", "language_switch") exactly matches the base
locale route file; implement a small script (e.g., routes_parity_check) that
loads each locale JSON, computes set differences against the base locale
(missing and extra keys), prints human-readable diffs and exits non-zero on
mismatch, and wire that script into the CI pipeline to run on PRs so any future
drift in route keys fails the build.
In `@scripts/manual-upgrade.php`:
- Around line 260-269: The POST password check needs rate-limiting: implement
per-session and per-IP attempt tracking and cooldown around the existing check
that uses UPGRADE_PASSWORD and sets $_SESSION['upgrade_auth']; on each failed
attempt increment counters (e.g. $_SESSION['upgrade_attempts'] and
$_SESSION['upgrade_last_attempt']) and persist per-IP attempts (e.g. simple
flatfile or APCu keyed by $_SERVER['REMOTE_ADDR']) and if limits are exceeded
return an $error with remaining cooldown; on successful hash_equals reset the
session counters and regenerate id as you already do; ensure timestamps are
stored to enforce a cooldown window and that counters decay after the window to
avoid permanent lockout.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
composer.lockis excluded by!**/*.lock
📒 Files selected for processing (41)
.htaccessREADME.mdapp/Controllers/FrontendController.phpapp/Controllers/LibriController.phpapp/Controllers/UpdateController.phpapp/Models/BookRepository.phpapp/Routes/web.phpapp/Support/I18n.phpapp/Support/Updater.phpapp/Views/admin/updates.phpapp/Views/autori/index.phpapp/Views/editori/index.phpapp/Views/events/index.phpapp/Views/frontend/event-detail.phpapp/Views/frontend/events.phpapp/Views/frontend/home-sections/events.phpapp/Views/frontend/home-sections/genre_carousel.phpapp/Views/frontend/home.phpapp/Views/libri/index.phpapp/Views/libri/partials/book_form.phpapp/Views/libri/scheda_libro.phpapp/Views/prestiti/index.phpapp/Views/utenti/index.phpcomposer.jsonfrontend/js/vendor.jsinstaller/classes/Installer.phpinstaller/database/data_de_DE.sqlinstaller/index.phpinstaller/steps/step0.phplocale/de_DE.jsonlocale/routes_de_DE.jsonpublic/assets/main.csspublic/assets/vendor.bundle.jsscripts/.user.iniscripts/create-release.shscripts/manual-update.phpscripts/manual-upgrade.phpvendor/composer/autoload_files.phpvendor/composer/autoload_static.phpvendor/composer/installed.jsonvendor/composer/installed.php
💤 Files with no reviewable changes (1)
- scripts/manual-update.php
| RewriteCond %{REQUEST_URI} (^|/)installer(/|$) [NC,OR] | ||
| RewriteCond %{REQUEST_URI} (^|/)scripts/manual-upgrade\.php$ [NC] | ||
| RewriteRule ^ - [L] |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Locate upgrade/installer entrypoints ==="
fd manual-upgrade.php --type f
fd -g '*.php' installer --type f | head -20
echo
echo "=== Check manual-upgrade authentication and request guards ==="
rg -n -C2 "password|hash_equals|password_verify|UPGRADE|REQUEST_METHOD|csrf|token|die\(|exit\(" scripts/manual-upgrade.php 2>/dev/null || echo "File not found or no matches"
echo
echo "=== Check installer lockout after installation ==="
rg -n -C2 "\.installed|already installed|isInstalled|redirect|forbidden|deny|exit\(" installer 2>/dev/null || echo "No matches found in installer directory"
echo
echo "=== List all files in installer directory (if exists) ==="
find . -type d -name "installer" -o -name "scripts" | head -10Repository: fabiodalez-dev/Pinakes
Length of output: 12733
🏁 Script executed:
#!/bin/bash
echo "=== Check force parameter authentication in installer/index.php ==="
sed -n '103,220p' installer/index.php
echo
echo "=== Check if there's any admin authentication check ==="
rg -n "admin|authenticate|authorized|session|check.*auth" installer/index.php | head -20Repository: fabiodalez-dev/Pinakes
Length of output: 7097
Verify that the default password in scripts/manual-upgrade.php is explicitly changed before production deployment.
The .installed lock file properly protects the installer, and the ?force=1 parameter requires admin authentication—both are sound guards. However, manual-upgrade.php relies entirely on developer action: the default password 'pinakes2026' must be manually changed before uploading. This requirement is documented only in comments (lines 21–23), not enforced by code. Add an explicit check that fails the script if the password remains at its default value, or require the password to be provided via .env rather than hardcoded.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.htaccess around lines 18 - 20, The manual-upgrade.php script currently
relies on a hardcoded default password 'pinakes2026' (commented) which must be
changed manually; update scripts/manual-upgrade.php to prevent accidental
production use by (a) loading the upgrade password from an environment variable
(e.g., UPGRADE_PASSWORD) instead of a hardcoded string and (b) adding an
explicit runtime check in the authentication logic that aborts/returns an error
if the configured password equals the known default 'pinakes2026' or is
empty—locate the password variable/constant and the authentication block in
manual-upgrade.php and implement the env lookup plus the guard that fails fast
when the default is detected.
- BookRepository: remove min_range=>1 from year validation to allow BCE years - Updater: add auth retry (401/403) without token for releases and download - Updater: validate releases response is indexed array via array_is_list() - Installer: move beginTransaction() inside try block for proper error handling - manual-upgrade.php: fail fast if credentials file write fails - genre-bugs test: replace undefined dbQuery() with admin API delete call
phpstan as a require-dev dependency caused the autoloader to reference vendor/phpstan/bootstrap.php, which broke every production ZIP that didn't run composer install --no-dev first. Since phpstan is installed system-wide (~/.composer/vendor/bin/phpstan), remove it from the project's composer.json entirely. The pre-commit hook and the code editor hook already fall back to the global binary. Also updates vendor dependencies to latest compatible versions.
|
@CodeRabbit full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 17
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
vendor/slim/psr7/src/Cookies.php (1)
1-176:⚠️ Potential issue | 🔴 CriticalDo not commit vendor files to git or modify vendor code directly.
The entire
vendor/directory (2400+ files) is currently tracked in git without a blanket.gitignoreentry. This creates multiple critical issues:
- Modified vendor files will be lost or cause conflicts on
composer installorcomposer update- Repository bloat: Vendor code should never be version-controlled; only
composer.lockis needed for reproducibility- Broken dependency management: Composer expects to control the vendor directory; tracking it defeats this purpose
Required actions:
- Add
vendor/to.gitignoreimmediately (replace selective vendor ignores)- Revert all modifications to vendor files, including the changes to
vendor/slim/psr7/src/Cookies.php- Remove the vendor directory from git history using
git rm -r --cached vendor/and commit- If the upstream library has a bug, submit a PR to slimphp/Slim-Psr7, fork the package, or use middleware/wrapper classes
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/slim/psr7/src/Cookies.php` around lines 1 - 176, The PR modified tracked vendor files (example: class Slim\Psr7\Cookies, method toHeader in vendor/slim/psr7/src/Cookies.php); do not commit vendor changes — add "vendor/" to .gitignore, revert all edits to the vendor directory (restore from composer or HEAD), run git rm -r --cached vendor/ and commit that removal, then run composer install to repopulate vendor; if you need behavior changes in Cookies::toHeader or other vendor APIs, implement them in your own wrapper/middleware or open a PR against slimphp/Slim-Psr7 instead of editing vendor code.vendor/slim/csrf/composer.json (1)
1-53:⚠️ Potential issue | 🔴 CriticalRemove vendor directory from version control and add comprehensive .gitignore rule.
Vendor files are tracked in git, which violates Composer best practices. The current
.gitignorecontains only selective patterns (specific subdirectories and CHANGELOG files) rather than excluding the entirevendor/directory. This causes:
- Inflated repository size
- Merge conflicts during dependency updates
- Difficulty maintaining the codebase
Add
/vendor/to.gitignoreto exclude the entire vendor directory, then remove tracked vendor files from git using:git rm -r --cached vendor/ git commit -m "Remove vendor directory from version control"Developers will regenerate the vendor directory locally via
composer install.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/slim/csrf/composer.json` around lines 1 - 53, The repo is tracking the vendor/ directory; update the project .gitignore to include a top-level entry "/vendor/" to stop committing Composer-installed dependencies, then remove the currently tracked vendor files from git cache (e.g., run git rm -r --cached vendor/ and commit the removal) so future installs are generated via composer install; reference the vendor/ path and ensure composer.json remains unchanged.
♻️ Duplicate comments (3)
app/Controllers/FrontendController.php (1)
1340-1341:⚠️ Potential issue | 🟡 MinorRemove unused
$totalPagesingenreArchive.Line 1341 computes
$totalPages, but this variable is never used in the method.✂️ Minimal cleanup
$row = $stmt->get_result()->fetch_assoc(); $totalBooks = $row['total'] ?? 0; - $totalPages = ceil($totalBooks / $limit);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Controllers/FrontendController.php` around lines 1340 - 1341, In the genreArchive method of FrontendController, remove the unused computation of $totalPages (the line assigning $totalPages = ceil($totalBooks / $limit)); keep the $totalBooks calculation if it’s used elsewhere but delete the $totalPages variable and its assignment to eliminate the unused variable warning and dead code.installer/database/data_de_DE.sql (1)
290-291:⚠️ Potential issue | 🟡 MinorAvoid absolute
/admin/...links in seeded CMS HTML.Lines 290-291 still embed absolute admin paths (
/admin/...), which can break subfolder installs. Use base-path-aware placeholders or relative links.🔧 Minimal string-level fix
- href=\"/admin/cms/about-us\" + href=\"{{BASE_PATH}}/admin/cms/about-us\" - href=\"/admin/cms/privacy-policy\" + href=\"{{BASE_PATH}}/admin/cms/privacy-policy\"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@installer/database/data_de_DE.sql` around lines 290 - 291, Update the seeded HTML rows in the cms_pages INSERTs for slugs 'about-us' and 'privacy-policy' to remove hardcoded absolute admin paths (/admin/cms/about-us and /admin/cms/privacy-policy) and use the app's base-path-aware placeholder or a relative admin path instead (so subfolder installs work); modify the VALUES for the two INSERT IGNORE INTO `cms_pages` entries to replace those two links with the project's agreed placeholder (e.g., a template variable or APP_BASE_PATH + relative path) so the runtime will resolve the correct base path.app/Support/Updater.php (1)
780-781:⚠️ Potential issue | 🟠 MajorAdd unauthenticated retry to the
file_get_contentsfallback paths.Line 780 and Line 964 always send the Authorization header. If cURL is unavailable and the stored token is invalid/revoked, the updater can fail even though tokenless requests would work. This makes an optional token effectively mandatory on those paths.
🔧 Proposed fix
- $context = stream_context_create([ + $context = stream_context_create([ 'http' => [ 'method' => 'GET', 'header' => $this->getGitHubHeaders(), 'timeout' => 30, 'ignore_errors' => true ] ]); - - $response = `@file_get_contents`($url, false, $context); + $response = `@file_get_contents`($url, false, $context); + $status = 0; + /** `@var` array<int, string> $http_response_header */ + if (!empty($http_response_header[0]) && preg_match('/HTTP\/\d\.\d\s+(\d+)/', $http_response_header[0], $m)) { + $status = (int) $m[1]; + } + if (in_array($status, [401, 403], true) && $this->githubToken !== '') { + $savedToken = $this->githubToken; + $this->githubToken = ''; + try { + $context = stream_context_create([ + 'http' => [ + 'method' => 'GET', + 'header' => $this->getGitHubHeaders(), + 'timeout' => 30, + 'ignore_errors' => true + ] + ]); + $response = `@file_get_contents`($url, false, $context); + } finally { + $this->githubToken = $savedToken; + } + }Also applies to: 964-965
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Support/Updater.php` around lines 780 - 781, The fallback file_get_contents calls currently always include the Authorization header from getGitHubHeaders(), causing failures when the stored token is invalid; update the fallback logic in the Updater methods that use file_get_contents (the calls around where 'header' => $this->getGitHubHeaders(), 'timeout' => 30 are passed) to do an unauthenticated retry: first attempt file_get_contents with the current headers, and if that returns false or produces an HTTP auth error (401/403) then call file_get_contents again with headers that omit the Authorization value (build headers without the token or call a variant of getGitHubHeaders that excludes auth) and the same timeout so public tokenless requests succeed. Ensure this change is applied to both the occurrence around getGitHubHeaders() at the shown block and the similar block at lines ~964-965.
🧹 Nitpick comments (4)
vendor/tecnickcom/tcpdf/config/tcpdf_config.php (1)
11-11: Malformed copyright year range.Same pattern as other TCPDF files:
2004-2014 2026should be2004-2026. This is an upstream issue in TCPDF 6.11.0.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/config/tcpdf_config.php` at line 11, The copyright header in tcpdf_config.php contains a malformed year range string "2004-2014 2026"; update that header to the correct range "2004-2026" so it matches other TCPDF files (i.e., replace the "2004-2014 2026" token in the file with "2004-2026").vendor/tecnickcom/tcpdf/include/tcpdf_colors.php (1)
10-10: Malformed copyright year range in vendor file.The copyright line shows
2002-2013 2026which appears to be a merge artifact or upstream bug. The expected format would be2002-2026(consistent with other TCPDF headers like the README line 9).This is part of the TCPDF 6.11.0 vendor update. Since this is a third-party dependency, no action is required unless you want to report this upstream.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/include/tcpdf_colors.php` at line 10, Update the malformed copyright header in the TCPDF vendor file by replacing the incorrect year sequence "2002-2013 2026" with the correct hyphenated range "2002-2026" in the top-of-file copyright comment (the header comment string in tcpdf_colors.php) so the license header matches other TCPDF files.vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php (1)
100-107: Reset inode/retry state inclose()to avoid stale lifecycle state.
$inodeUrland$retryingcan remain stale across explicit close/reset cycles.♻️ Suggested patch
public function close(): void { if (null !== $this->url && \is_resource($this->stream)) { fclose($this->stream); } $this->stream = null; $this->dirCreated = null; + $this->inodeUrl = null; + $this->retrying = false; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php` around lines 100 - 107, The close() method currently nulls $this->stream and $this->dirCreated but leaves $this->inodeUrl and $this->retrying stale; update StreamHandler::close() to also reset $this->inodeUrl (set to null) and $this->retrying (set to false) so any subsequent open/reopen uses a fresh lifecycle state; locate the close() method and add clearing of $this->inodeUrl and $this->retrying alongside the existing $this->stream and $this->dirCreated resets.app/Controllers/LibriController.php (1)
541-542: Normalizenumero_pagineconsistently with the other numeric fields.Good addition in both create/update defaults. Consider explicitly casting/clamping
numero_paginenearpeso/prezzoconversion so invalid input handling is consistent in both flows.♻️ Suggested patch
@@ - $fields['peso'] = $fields['peso'] !== null && $fields['peso'] !== '' ? (float) $fields['peso'] : null; - $fields['prezzo'] = $fields['prezzo'] !== null && $fields['prezzo'] !== '' ? (float) $fields['prezzo'] : null; + $fields['peso'] = $fields['peso'] !== null && $fields['peso'] !== '' ? (float) $fields['peso'] : null; + $fields['prezzo'] = $fields['prezzo'] !== null && $fields['prezzo'] !== '' ? (float) $fields['prezzo'] : null; + $fields['numero_pagine'] = $fields['numero_pagine'] !== null && $fields['numero_pagine'] !== '' + ? max(1, (int) $fields['numero_pagine']) + : null;@@ - $fields['peso'] = $fields['peso'] !== null && $fields['peso'] !== '' ? (float) $fields['peso'] : null; - $fields['prezzo'] = $fields['prezzo'] !== null && $fields['prezzo'] !== '' ? (float) $fields['prezzo'] : null; + $fields['peso'] = $fields['peso'] !== null && $fields['peso'] !== '' ? (float) $fields['peso'] : null; + $fields['prezzo'] = $fields['prezzo'] !== null && $fields['prezzo'] !== '' ? (float) $fields['prezzo'] : null; + $fields['numero_pagine'] = $fields['numero_pagine'] !== null && $fields['numero_pagine'] !== '' + ? max(1, (int) $fields['numero_pagine']) + : null;Also applies to: 1047-1048
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Controllers/LibriController.php` around lines 541 - 542, Normalize numero_pagine the same way as peso/prezzo by explicitly casting and clamping it when you build defaults and during create/update flows: locate where 'numero_pagine' is set to null in the defaults and the conversion logic around the peso/prezzo handling (the methods that perform peso/prezzo conversion), and apply the same validation pattern (cast to int, enforce min 0 and optional max, and fallback to null or 0 as your other numeric fields do) so input is handled consistently in both create and update paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/Support/Updater.php`:
- Around line 921-954: The code accepts cURL response bodies even when HTTP
status >=400, so ensure responses with $curlErrno !== 0 or $httpCode >= 400 are
treated as failures: when the initial request yields $curlErrno !== 0 or
$httpCode >= 400 set $fileContent = false (instead of leaving any non-false
body), then attempt the retry without token and only assign $fileContent =
$retryContent if $retryContent !== false AND $retryCode >= 200 AND $retryCode <
400; otherwise keep $fileContent as false and log the error (use the existing
variables $curlErrno, $curlError, $httpCode, $retryContent, $retryCode and keep
using debugLog for warnings).
In `@app/Views/libri/index.php`:
- Line 839: The DataTable init uses window.getDtLanguage() directly which can
throw if the global helper is missing; change the code around the DataTable
options (the line containing language: window.getDtLanguage()) to call a safe
accessor like (typeof window.getDtLanguage === 'function' ?
window.getDtLanguage() : {}) or provide a sensible default (e.g. language: {} or
language: { url: '...' }) so table initialization won't crash on stale assets;
update the DataTable initialization block that references window.getDtLanguage
to use this guarded fallback.
In `@installer/steps/step0.php`:
- Around line 15-29: The locale fallback and allowed list use inconsistent short
code 'it'—update $allowedLanguages to use 'it_IT' instead of 'it', change the
fallback assignment for $selectedLanguage to 'it_IT', and update the match
expression that sets $_SESSION['installer_language'] to map 'it_IT' to 'it'
(keeping 'en_US' => 'en' and 'de_DE' => 'de'); ensure $_SESSION['app_locale']
continues to be set to $selectedLanguage and that this aligns with the project's
normalizeInstallerLocale() and canonical I18n locale codes.
In `@scripts/manual-upgrade.php`:
- Around line 575-578: The current preg_replace on $stmtSql removes every line
starting with -- inside the statement body; change it to remove only a leading
block of SQL comment lines so inline/embedded -- comments remain in multi-line
SQL. Locate the foreach over $statements and the $stmtSql manipulation (the
preg_replace and subsequent trim), and replace the pattern/logic so it strips
only an initial contiguous sequence of comment lines (a leading comment block) —
e.g. match from the start of the string for repeated lines that are purely
comments/whitespace and remove that chunk — leaving any -- lines that occur
after the first executable SQL untouched.
In `@tests/genre-bugs.spec.js`:
- Around line 305-306: The cleanup POST to
`${BASE}/admin/libri/elimina/${bookId}` currently isn't checked; update the test
to assert the request succeeded by capturing the response from page.request.post
and asserting its status is in the 2xx range (or the expected status code) and
optionally validating response body for success; reference the existing call to
page.request.post(`${BASE}/admin/libri/elimina/${bookId}`) and the variables
BASE and bookId so you add the assertions immediately after that call.
In `@vendor/laravel/serializable-closure/composer.json`:
- Around line 24-28: Add a new GitHub Actions workflow (e.g.,
test-new-majors.yml) that runs CI against the expanded dev-version combinations
introduced in composer.json: test with illuminate/support ^13.0, pestphp/pest
^4.0, and symfony/var-dumper ^8.0.0 by adding a matrix that varies PHP versions
(at least 8.1 and 8.2 to match existing release/test workflows) and separate
matrix entries for the new major versions of "illuminate/support",
"pestphp/pest", and "symfony/var-dumper"; ensure the job runs the project’s
existing composer install/test steps so these combinations are exercised in CI.
In `@vendor/laravel/serializable-closure/src/Support/ReflectionClosure.php`:
- Around line 1363-1433: The parser in verifyCandidateSignature incorrectly
flips from 'params' to 'body' on any ')' token, breaking param parsing for
defaults with parentheses; modify the token loop to track parenthesis nesting
(e.g., introduce $depth = 0), set $state = 'params' and $depth = 1 when the
first '(' is seen, increment $depth for additional '(' and decrement on ')', and
only switch $state to 'body' when in 'params' and $depth returns to 0; ensure
$params still collects T_VARIABLE when $state === 'params' and $vars collects
when $state === 'body' (skipping $this) so parameter counting and use/static
variable checks remain correct.
In `@vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php`:
- Around line 48-54: The constructor's timezone is assigned after computing
$this->nextRotation which causes rotation to use server default timezone; move
the assignment of $this->timezone before computing $this->nextRotation, and
change getNextRotation() to accept a DateTimeZone (or null) parameter and use
that timezone when building the DateTimeImmutable for the next rotation; update
the constructor call to pass $this->timezone into
getNextRotation($this->timezone) and adjust any other internal calls to
getNextRotation to supply the configured timezone so filename generation and
rotation boundaries use the same timezone.
In `@vendor/tecnickcom/tcpdf/config/tcpdf_config.php`:
- Line 117: The PDF_HEADER_STRING constant in tcpdf_config.php is corrupted
("by2026 Nicola Asuni..."); update the constant PDF_HEADER_STRING to the correct
header text (e.g., "by Nicola Asuni - Tecnick.com\nwww.tcpdf.org") to restore
the intended PDF header, or alternatively override this constant in your
application configuration if you must avoid editing vendor files; after fixing,
run a PDF generation test to confirm the header displays correctly and consider
reporting the typo upstream to the TCPDF maintainers.
In `@vendor/tecnickcom/tcpdf/include/barcodes/datamatrix.php`:
- Line 10: Update the malformed copyright year range in the file's header
comment: replace the string "2010-2014 2026" with the corrected range
"2010-2026" in the datamatrix.php header comment so the copyright line reads a
single valid range; locate the header comment at the top of
vendor/tecnickcom/tcpdf/include/barcodes/datamatrix.php and edit that copyright
line accordingly.
In `@vendor/tecnickcom/tcpdf/include/barcodes/pdf417.php`:
- Line 10: Replace the malformed copyright header string "Copyright (C)
2010-2013 2026 Nicola Asuni - Tecnick.com LTD" in pdf417.php with the correct
upstream text (e.g., match the official TCPDF header such as "Copyright (C)
2010–2010 Nicola Asuni - Tecnick.com LTD") or, if this year-range change was
intentional, add a short inline comment explaining the local modification and
its rationale; update only the header line containing that exact string so the
vendor file matches upstream or documents the deviation.
In `@vendor/tecnickcom/tcpdf/include/tcpdf_fonts.php`:
- Line 1387: The checksum adjustment for subsetted TTF is being written to the
wrong bytes: locate the write that composes $font using $table['head']['offset']
and $offset (the line that builds $font with pack('N', $checkSumAdjustment)) and
change the slice boundaries from +4..+8 to +8..+12 so pack('N',
$checkSumAdjustment) is inserted at bytes 0x08–0x0B of the head table (i.e.
replace the substr offsets +4 and +8 with +8 and +12 respectively when
assembling $font).
In `@vendor/tecnickcom/tcpdf/include/tcpdf_images.php`:
- Line 277: Replace the misspelled diagnostic comment "Unknownn" with "Unknown"
in the PNG parser comments: search for the literal "Unknownn" in
vendor/tecnickcom/tcpdf/include/tcpdf_images.php (occurrences referenced near
the PNG parsing logic/comments around the PNG handling functions) and update
each comment (e.g., the lines currently reading "// Unknownn compression method"
and "// Unknownn filter method") to read "// Unknown ... method" — i.e., change
"Unknownn" to "Unknown" to fix the typo.
In `@vendor/tecnickcom/tcpdf/tcpdf_autoconfig.php`:
- Line 157: The PDF header default constant PDF_HEADER_STRING contains a typo
"by2026 Nicola Asuni - Tecnick.com\nwww.tcpdf.org"; update the definition of
PDF_HEADER_STRING in tcpdf_autoconfig.php to remove the accidental "2026" so it
reads "by Nicola Asuni - Tecnick.com\nwww.tcpdf.org" (update the string assigned
to the PDF_HEADER_STRING constant).
In `@vendor/tecnickcom/tcpdf/tcpdf.php`:
- Around line 6925-6927: In fitBlock(), the clamp that checks if ($y + $h) >
$this->PageBreakTrigger + $this->bMargin allows content into the footer margin
when fitonpage is used; change the clamp to compare against
$this->PageBreakTrigger (remove the added + $this->bMargin) so the computed $h
and derived $w ($h * $ratio_wh) are constrained to the PageBreakTrigger
boundary; update any related calculations in fitBlock() that adjust $h/$w when
the clamp fires to use $this->PageBreakTrigger and ensure fitonpage honors the
configured bottom margin.
- Around line 23733-23737: The preg_split used to parse SVG path parameters is
using a capturing group which keeps delimiters (commas) in $rawparams and causes
misalignment when later passed to getHTMLUnitToUnits(); fix by changing the
split to not capture delimiters (e.g., remove the parentheses in the regex so it
splits on /[,\s]+/si) or use a non-capturing pattern and/or PREG_SPLIT_NO_EMPTY,
and ensure the $rawparams filter removes empty and delimiter tokens so only
numeric parameter strings reach getHTMLUnitToUnits().
- Around line 2909-2914: The setCompression method currently sets
$this->compress = false and immediately returns, making the gzcompress check and
the $compress parameter ineffective; remove the early "return;" so the method
can check function_exists('gzcompress') and set $this->compress = true when
$compress is true and gzcompress is available (otherwise leave it false),
ensuring setCompression($compress=true) behaves as intended in function
setCompression and uses the $compress argument and PHP's gzcompress availability
to toggle the $this->compress property.
---
Outside diff comments:
In `@vendor/slim/csrf/composer.json`:
- Around line 1-53: The repo is tracking the vendor/ directory; update the
project .gitignore to include a top-level entry "/vendor/" to stop committing
Composer-installed dependencies, then remove the currently tracked vendor files
from git cache (e.g., run git rm -r --cached vendor/ and commit the removal) so
future installs are generated via composer install; reference the vendor/ path
and ensure composer.json remains unchanged.
In `@vendor/slim/psr7/src/Cookies.php`:
- Around line 1-176: The PR modified tracked vendor files (example: class
Slim\Psr7\Cookies, method toHeader in vendor/slim/psr7/src/Cookies.php); do not
commit vendor changes — add "vendor/" to .gitignore, revert all edits to the
vendor directory (restore from composer or HEAD), run git rm -r --cached vendor/
and commit that removal, then run composer install to repopulate vendor; if you
need behavior changes in Cookies::toHeader or other vendor APIs, implement them
in your own wrapper/middleware or open a PR against slimphp/Slim-Psr7 instead of
editing vendor code.
---
Duplicate comments:
In `@app/Controllers/FrontendController.php`:
- Around line 1340-1341: In the genreArchive method of FrontendController,
remove the unused computation of $totalPages (the line assigning $totalPages =
ceil($totalBooks / $limit)); keep the $totalBooks calculation if it’s used
elsewhere but delete the $totalPages variable and its assignment to eliminate
the unused variable warning and dead code.
In `@app/Support/Updater.php`:
- Around line 780-781: The fallback file_get_contents calls currently always
include the Authorization header from getGitHubHeaders(), causing failures when
the stored token is invalid; update the fallback logic in the Updater methods
that use file_get_contents (the calls around where 'header' =>
$this->getGitHubHeaders(), 'timeout' => 30 are passed) to do an unauthenticated
retry: first attempt file_get_contents with the current headers, and if that
returns false or produces an HTTP auth error (401/403) then call
file_get_contents again with headers that omit the Authorization value (build
headers without the token or call a variant of getGitHubHeaders that excludes
auth) and the same timeout so public tokenless requests succeed. Ensure this
change is applied to both the occurrence around getGitHubHeaders() at the shown
block and the similar block at lines ~964-965.
In `@installer/database/data_de_DE.sql`:
- Around line 290-291: Update the seeded HTML rows in the cms_pages INSERTs for
slugs 'about-us' and 'privacy-policy' to remove hardcoded absolute admin paths
(/admin/cms/about-us and /admin/cms/privacy-policy) and use the app's
base-path-aware placeholder or a relative admin path instead (so subfolder
installs work); modify the VALUES for the two INSERT IGNORE INTO `cms_pages`
entries to replace those two links with the project's agreed placeholder (e.g.,
a template variable or APP_BASE_PATH + relative path) so the runtime will
resolve the correct base path.
---
Nitpick comments:
In `@app/Controllers/LibriController.php`:
- Around line 541-542: Normalize numero_pagine the same way as peso/prezzo by
explicitly casting and clamping it when you build defaults and during
create/update flows: locate where 'numero_pagine' is set to null in the defaults
and the conversion logic around the peso/prezzo handling (the methods that
perform peso/prezzo conversion), and apply the same validation pattern (cast to
int, enforce min 0 and optional max, and fallback to null or 0 as your other
numeric fields do) so input is handled consistently in both create and update
paths.
In `@vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php`:
- Around line 100-107: The close() method currently nulls $this->stream and
$this->dirCreated but leaves $this->inodeUrl and $this->retrying stale; update
StreamHandler::close() to also reset $this->inodeUrl (set to null) and
$this->retrying (set to false) so any subsequent open/reopen uses a fresh
lifecycle state; locate the close() method and add clearing of $this->inodeUrl
and $this->retrying alongside the existing $this->stream and $this->dirCreated
resets.
In `@vendor/tecnickcom/tcpdf/config/tcpdf_config.php`:
- Line 11: The copyright header in tcpdf_config.php contains a malformed year
range string "2004-2014 2026"; update that header to the correct range
"2004-2026" so it matches other TCPDF files (i.e., replace the "2004-2014 2026"
token in the file with "2004-2026").
In `@vendor/tecnickcom/tcpdf/include/tcpdf_colors.php`:
- Line 10: Update the malformed copyright header in the TCPDF vendor file by
replacing the incorrect year sequence "2002-2013 2026" with the correct
hyphenated range "2002-2026" in the top-of-file copyright comment (the header
comment string in tcpdf_colors.php) so the license header matches other TCPDF
files.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
composer.lockis excluded by!**/*.lock
📒 Files selected for processing (104)
.htaccessREADME.mdapp/Controllers/FrontendController.phpapp/Controllers/LibriController.phpapp/Controllers/UpdateController.phpapp/Models/BookRepository.phpapp/Routes/web.phpapp/Support/I18n.phpapp/Support/Updater.phpapp/Views/admin/updates.phpapp/Views/autori/index.phpapp/Views/editori/index.phpapp/Views/events/index.phpapp/Views/frontend/event-detail.phpapp/Views/frontend/events.phpapp/Views/frontend/home-sections/events.phpapp/Views/frontend/home-sections/genre_carousel.phpapp/Views/frontend/home.phpapp/Views/libri/index.phpapp/Views/libri/partials/book_form.phpapp/Views/libri/scheda_libro.phpapp/Views/prestiti/index.phpapp/Views/utenti/index.phpfrontend/js/vendor.jsinstaller/classes/Installer.phpinstaller/database/data_de_DE.sqlinstaller/index.phpinstaller/steps/step0.phplocale/de_DE.jsonlocale/routes_de_DE.jsonpublic/assets/main.csspublic/assets/vendor.bundle.jsscripts/.user.iniscripts/create-release.shscripts/manual-update.phpscripts/manual-upgrade.phptests/genre-bugs.spec.jsvendor/composer/autoload_classmap.phpvendor/composer/autoload_files.phpvendor/composer/autoload_static.phpvendor/composer/installed.jsonvendor/composer/installed.phpvendor/graham-campbell/result-type/composer.jsonvendor/laravel/serializable-closure/composer.jsonvendor/laravel/serializable-closure/src/Serializers/Native.phpvendor/laravel/serializable-closure/src/Support/ReflectionClosure.phpvendor/monolog/monolog/README.mdvendor/monolog/monolog/composer.jsonvendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.phpvendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.phpvendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.phpvendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.phpvendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.phpvendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.phpvendor/monolog/monolog/src/Monolog/Handler/CubeHandler.phpvendor/monolog/monolog/src/Monolog/Handler/Curl/Util.phpvendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.phpvendor/monolog/monolog/src/Monolog/Handler/Handler.phpvendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.phpvendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.phpvendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.phpvendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.phpvendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.phpvendor/monolog/monolog/src/Monolog/Handler/StreamHandler.phpvendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.phpvendor/monolog/monolog/src/Monolog/Handler/TestHandler.phpvendor/slim/csrf/composer.jsonvendor/slim/psr7/composer.jsonvendor/slim/psr7/src/Cookies.phpvendor/slim/slim/MAINTAINERS.mdvendor/slim/slim/Slim/App.phpvendor/slim/slim/composer.jsonvendor/slim/slim/psalm.xmlvendor/tecnickcom/tcpdf/CHANGELOG.TXTvendor/tecnickcom/tcpdf/LICENSE.TXTvendor/tecnickcom/tcpdf/README.mdvendor/tecnickcom/tcpdf/VERSIONvendor/tecnickcom/tcpdf/composer.jsonvendor/tecnickcom/tcpdf/config/tcpdf_config.phpvendor/tecnickcom/tcpdf/fonts/ae_fonts_2.0/READMEvendor/tecnickcom/tcpdf/fonts/freefont-20100919/COPYINGvendor/tecnickcom/tcpdf/fonts/freefont-20100919/CREDITSvendor/tecnickcom/tcpdf/fonts/freefont-20120503/COPYINGvendor/tecnickcom/tcpdf/fonts/freefont-20120503/CREDITSvendor/tecnickcom/tcpdf/fonts/freefont-20120503/READMEvendor/tecnickcom/tcpdf/fonts/freefont-20120503/TROUBLESHOOTINGvendor/tecnickcom/tcpdf/include/barcodes/datamatrix.phpvendor/tecnickcom/tcpdf/include/barcodes/pdf417.phpvendor/tecnickcom/tcpdf/include/barcodes/qrcode.phpvendor/tecnickcom/tcpdf/include/tcpdf_colors.phpvendor/tecnickcom/tcpdf/include/tcpdf_filters.phpvendor/tecnickcom/tcpdf/include/tcpdf_font_data.phpvendor/tecnickcom/tcpdf/include/tcpdf_fonts.phpvendor/tecnickcom/tcpdf/include/tcpdf_images.phpvendor/tecnickcom/tcpdf/include/tcpdf_static.phpvendor/tecnickcom/tcpdf/tcpdf.phpvendor/tecnickcom/tcpdf/tcpdf_autoconfig.phpvendor/tecnickcom/tcpdf/tcpdf_barcodes_1d.phpvendor/tecnickcom/tcpdf/tcpdf_barcodes_2d.phpvendor/tecnickcom/tcpdf/tools/tcpdf_addfont.phpvendor/vlucas/phpdotenv/composer.jsonvendor/vlucas/phpdotenv/src/Parser/EntryParser.phpvendor/vlucas/phpdotenv/src/Repository/Adapter/EnvConstAdapter.phpvendor/vlucas/phpdotenv/src/Repository/Adapter/ServerConstAdapter.php
💤 Files with no reviewable changes (7)
- vendor/vlucas/phpdotenv/src/Repository/Adapter/ServerConstAdapter.php
- vendor/slim/slim/MAINTAINERS.md
- vendor/vlucas/phpdotenv/src/Repository/Adapter/EnvConstAdapter.php
- vendor/slim/slim/psalm.xml
- vendor/vlucas/phpdotenv/src/Parser/EntryParser.php
- vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php
- scripts/manual-update.php
| } | ||
| if (ord(fread($f, 1)) != 0) { | ||
| // Unknown compression method | ||
| // Unknownn compression method |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n "Unknownn compression method|Unknownn filter method" vendor/tecnickcom/tcpdf/include/tcpdf_images.phpRepository: fabiodalez-dev/Pinakes
Length of output: 171
🏁 Script executed:
# Also check the specific lines mentioned
sed -n '277p;282p;334p' vendor/tecnickcom/tcpdf/include/tcpdf_images.phpRepository: fabiodalez-dev/Pinakes
Length of output: 159
🏁 Script executed:
# Check broader context around these lines
sed -n '275,280p;280,285p;332,336p' vendor/tecnickcom/tcpdf/include/tcpdf_images.phpRepository: fabiodalez-dev/Pinakes
Length of output: 403
Fix typo regression in PNG parser diagnostics.
Lines 277, 282, and 334 contain "Unknownn" (double n) in diagnostic comments. Correct to "Unknown" for consistency.
Affected locations
- Line 277:
// Unknownn compression method - Line 282:
// Unknownn filter method - Line 334:
// Unknownn filter method
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vendor/tecnickcom/tcpdf/include/tcpdf_images.php` at line 277, Replace the
misspelled diagnostic comment "Unknownn" with "Unknown" in the PNG parser
comments: search for the literal "Unknownn" in
vendor/tecnickcom/tcpdf/include/tcpdf_images.php (occurrences referenced near
the PNG parsing logic/comments around the PNG handling functions) and update
each comment (e.g., the lines currently reading "// Unknownn compression method"
and "// Unknownn filter method") to read "// Unknown ... method" — i.e., change
"Unknownn" to "Unknown" to fix the typo.
|
|
||
| if (!defined('PDF_HEADER_STRING')) { | ||
| define ('PDF_HEADER_STRING', "by Nicola Asuni - Tecnick.com\nwww.tcpdf.org"); | ||
| define ('PDF_HEADER_STRING', "by2026 Nicola Asuni - Tecnick.com\nwww.tcpdf.org"); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n "PDF_HEADER_STRING|by2026" vendor/tecnickcom/tcpdf/tcpdf_autoconfig.phpRepository: fabiodalez-dev/Pinakes
Length of output: 193
Fix default PDF header text typo.
Line 157 uses by2026, which appears to be an accidental concatenation and will leak into generated document headers. Should be by.
Proposed fix
-define ('PDF_HEADER_STRING', "by2026 Nicola Asuni - Tecnick.com\nwww.tcpdf.org");
+define ('PDF_HEADER_STRING', "by Nicola Asuni - Tecnick.com\nwww.tcpdf.org");📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| define ('PDF_HEADER_STRING', "by2026 Nicola Asuni - Tecnick.com\nwww.tcpdf.org"); | |
| define ('PDF_HEADER_STRING', "by Nicola Asuni - Tecnick.com\nwww.tcpdf.org"); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vendor/tecnickcom/tcpdf/tcpdf_autoconfig.php` at line 157, The PDF header
default constant PDF_HEADER_STRING contains a typo "by2026 Nicola Asuni -
Tecnick.com\nwww.tcpdf.org"; update the definition of PDF_HEADER_STRING in
tcpdf_autoconfig.php to remove the accidental "2026" so it reads "by Nicola
Asuni - Tecnick.com\nwww.tcpdf.org" (update the string assigned to the
PDF_HEADER_STRING constant).
| public function setCompression($compress=true) { | ||
| $this->compress = false; | ||
| $this->compress = false; return; | ||
| if (function_exists('gzcompress')) { | ||
| if ($compress) { | ||
| if ( !$this->pdfa_mode) { | ||
| $this->compress = true; | ||
| } | ||
| $this->compress = true; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="vendor/tecnickcom/tcpdf/tcpdf.php"
echo "[local]"
rg -n -C2 'public function setCompression|\$this->compress = false; return;|\$this->compress = true;|pdfa_mode' "$FILE"
echo ""
echo "[upstream 6.10.0]"
curl -fsSL https://raw.githubusercontent.com/tecnickcom/TCPDF/6.10.0/tcpdf.php \
| rg -n -C2 'public function setCompression|\$this->compress = false;|\$this->compress = true;|pdfa_mode'
echo ""
echo "[upstream 6.11.0]"
curl -fsSL https://raw.githubusercontent.com/tecnickcom/TCPDF/6.11.0/tcpdf.php \
| rg -n -C2 'public function setCompression|\$this->compress = false; return;|\$this->compress = true;|pdfa_mode'Repository: fabiodalez-dev/Pinakes
Length of output: 25553
setCompression() is disabled by an early return on line 2910.
The early return; statement prevents the function from ever enabling compression. This is a regression from upstream version 6.10.0, which had the correct implementation. The rest of the method (lines 2911–2915) is unreachable dead code.
Proposed fix
public function setCompression($compress=true) {
- $this->compress = false; return;
- if (function_exists('gzcompress')) {
- if ($compress) {
- $this->compress = true;
- }
- }
+ $this->compress = false;
+ if (function_exists('gzcompress') && $compress) {
+ if (!$this->pdfa_mode) {
+ $this->compress = true;
+ }
+ }
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vendor/tecnickcom/tcpdf/tcpdf.php` around lines 2909 - 2914, The
setCompression method currently sets $this->compress = false and immediately
returns, making the gzcompress check and the $compress parameter ineffective;
remove the early "return;" so the method can check function_exists('gzcompress')
and set $this->compress = true when $compress is true and gzcompress is
available (otherwise leave it false), ensuring setCompression($compress=true)
behaves as intended in function setCompression and uses the $compress argument
and PHP's gzcompress availability to toggle the $this->compress property.
| if (($y + $h) > $this->PageBreakTrigger + $this->bMargin) { | ||
| $h = $this->PageBreakTrigger + $this->bMargin - $y; | ||
| $w = ($h * $ratio_wh); |
There was a problem hiding this comment.
fitBlock() clamp appears to allow content into footer margin.
Line 6925-Line 6926 uses PageBreakTrigger + bMargin (effectively page bottom), which can violate the configured bottom margin when fitonpage is true.
📏 Proposed fix
- if (($y + $h) > $this->PageBreakTrigger + $this->bMargin) {
- $h = $this->PageBreakTrigger + $this->bMargin - $y;
+ if (($y + $h) > $this->PageBreakTrigger) {
+ $h = $this->PageBreakTrigger - $y;
$w = ($h * $ratio_wh);
}Use this script to compare current behavior with upstream versions:
#!/bin/bash
set -euo pipefail
FILE="vendor/tecnickcom/tcpdf/tcpdf.php"
echo "[local fitBlock clamp]"
rg -n -C3 'protected function fitBlock|PageBreakTrigger \+ \$this->bMargin|PageBreakTrigger - \$y' "$FILE"
echo "[upstream 6.10.0 fitBlock clamp]"
curl -fsSL https://raw.githubusercontent.com/tecnickcom/TCPDF/6.10.0/tcpdf.php \
| rg -n -C3 'protected function fitBlock|\(\$y \+ \$h\) > \$this->PageBreakTrigger'
echo "[upstream 6.11.0 fitBlock clamp]"
curl -fsSL https://raw.githubusercontent.com/tecnickcom/TCPDF/6.11.0/tcpdf.php \
| rg -n -C3 'protected function fitBlock|\(\$y \+ \$h\) > \$this->PageBreakTrigger(\s*\+\s*\$this->bMargin)?'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vendor/tecnickcom/tcpdf/tcpdf.php` around lines 6925 - 6927, In fitBlock(),
the clamp that checks if ($y + $h) > $this->PageBreakTrigger + $this->bMargin
allows content into the footer margin when fitonpage is used; change the clamp
to compare against $this->PageBreakTrigger (remove the added + $this->bMargin)
so the computed $h and derived $w ($h * $ratio_wh) are constrained to the
PageBreakTrigger boundary; update any related calculations in fitBlock() that
adjust $h/$w when the clamp fires to use $this->PageBreakTrigger and ensure
fitonpage honors the configured bottom margin.
Lightweight fixed button in bottom-right corner that appears after scrolling 400px. Uses pure JS DOM creation appended to body to avoid Tailwind specificity conflicts and CSS containment issues with position:fixed. Rose accent on hover matches app theme. Closes #72
- Updater: set $fileContent = false on HTTP >= 400 before retry logic - Updater: add unauthenticated retry to file_get_contents fallback paths - DataTables: guard getDtLanguage() with typeof check in all 5 views - Installer step0: use canonical it_IT locale code instead of short 'it' - manual-upgrade: strip only leading SQL comment block, not inline comments - genre-bugs test: assert cleanup response status - LibriController: normalize numero_pagine consistently with peso/prezzo - CMS seed data: remove hardcoded /admin/cms/ links from all 3 locales - FrontendController $totalPages: NOT removed (used by archive.php view)
Move keyboard shortcuts from books-only page to global layout with: - Keyboard icon button in admin header toolbar (hidden on mobile) - Two-section modal: Navigation (global) + Books Management (contextual) - GitHub-style G-prefix navigation (G→D dashboard, G→B books, etc.) - ? key opens shortcuts help from any admin page - Mac-aware modifier key labels (⌘ vs Ctrl) - Fix E2E test to use correct #shortcuts-modal selector
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 10
♻️ Duplicate comments (6)
vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php (1)
48-54:⚠️ Potential issue | 🟠 MajorRotation cutoff still ignores configured timezone.
Line 53 computes
nextRotationbefore Line 54 assigns$this->timezone, andgetNextRotation()(Lines 226-232) still builds dates in server-default timezone. This makes rotation happen at a different boundary than filename dating (Line 183).Suggested fix
- $this->nextRotation = $this->getNextRotation(); $this->timezone = $timezone; + $this->nextRotation = $this->getNextRotation();- protected function getNextRotation(): \DateTimeImmutable + protected function getNextRotation(): \DateTimeImmutable { + $now = new \DateTimeImmutable('now', $this->timezone); + return match (str_replace(['/','_','.'], '-', $this->dateFormat)) { - self::FILE_PER_MONTH => (new \DateTimeImmutable('first day of next month'))->setTime(0, 0, 0), - self::FILE_PER_YEAR => (new \DateTimeImmutable('first day of January next year'))->setTime(0, 0, 0), - default => (new \DateTimeImmutable('tomorrow'))->setTime(0, 0, 0), + self::FILE_PER_MONTH => $now->modify('first day of next month')->setTime(0, 0, 0), + self::FILE_PER_YEAR => $now->modify('first day of January next year')->setTime(0, 0, 0), + default => $now->modify('tomorrow')->setTime(0, 0, 0), }; }#!/bin/bash # Read-only verification of timezone flow in constructor + rotation boundary nl -ba vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php | sed -n '48,58p;123,130p;178,186p;226,233p'Also applies to: 127-127, 226-232
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php` around lines 48 - 54, The constructor computes $this->nextRotation by calling getNextRotation() before assigning $this->timezone, causing rotation cutoff to ignore the configured timezone; fix it by assigning $this->timezone (the DateTimeZone|null parameter) before calling $this->getNextRotation() in __construct() (or alternatively change getNextRotation() to accept a timezone argument), ensuring getNextRotation() uses the configured timezone when calculating rotation boundaries and keeping filename dating in setFilenameFormat()/getDatedFilename() consistent.vendor/tecnickcom/tcpdf/tcpdf_autoconfig.php (1)
157-157:⚠️ Potential issue | 🟡 MinorFix visible typo in default PDF header string.
Line 157 contains
by2026, which will leak into generated PDF headers. It should beby.🩹 Proposed fix
- define ('PDF_HEADER_STRING', "by2026 Nicola Asuni - Tecnick.com\nwww.tcpdf.org"); + define ('PDF_HEADER_STRING', "by Nicola Asuni - Tecnick.com\nwww.tcpdf.org");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/tcpdf_autoconfig.php` at line 157, The PDF header constant PDF_HEADER_STRING contains a typo "by2026" that will appear in generated PDFs; update the constant definition for PDF_HEADER_STRING (the define call) to use "by" instead of "by2026" so the string reads "by Nicola Asuni - Tecnick.com\nwww.tcpdf.org". Ensure you update the define('PDF_HEADER_STRING', ...) entry where it's declared.app/Support/Updater.php (1)
587-601:⚠️ Potential issue | 🟠 MajorAuth retry in
makeGitHubRequest()is too restrictive compared to other updater paths.At Line 590, retry is allowed only for specific message text. This can still block update checks when auth fails with a different 401/403 payload, while
getAllReleases()anddownloadUpdate()already retry by status alone.💡 Suggested fix
- if ($allowAuthRetry && $this->githubToken !== '' && in_array($statusCode, [401, 403], true)) { - $errorData = json_decode($response, true); - $message = strtolower((string) ($errorData['message'] ?? '')); - if (str_contains($message, 'bad credentials') || str_contains($message, 'requires authentication')) { - $this->debugLog('WARNING', 'Token GitHub non valido, retry senza token', [ - 'status_code' => $statusCode, - ]); - $savedToken = $this->githubToken; - $this->githubToken = ''; - try { - return $this->makeGitHubRequest($url, false); - } finally { - $this->githubToken = $savedToken; - } - } - } + if ($allowAuthRetry && $this->githubToken !== '' && in_array($statusCode, [401, 403], true)) { + $this->debugLog('WARNING', 'Auth GitHub fallita, retry senza token', [ + 'status_code' => $statusCode, + ]); + $savedToken = $this->githubToken; + $this->githubToken = ''; + try { + return $this->makeGitHubRequest($url, false); + } finally { + $this->githubToken = $savedToken; + } + }#!/bin/bash set -euo pipefail echo "=== makeGitHubRequest retry gate ===" rg -n -C3 "in_array\\(\\$statusCode, \\[401, 403\\], true\\)|bad credentials|requires authentication" app/Support/Updater.php echo echo "=== Other updater retry branches (status-only) ===" rg -n -C3 "Releases auth fallito, retry senza token|Download auth fallito, retry senza token|in_array\\(\\$httpCode, \\[401, 403\\], true\\)" app/Support/Updater.php🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Support/Updater.php` around lines 587 - 601, The auth-retry in makeGitHubRequest is too restrictive because it only retries when the response message contains specific text; change it to mirror getAllReleases()/downloadUpdate by retrying on status codes alone: when $allowAuthRetry is true, $this->githubToken is non-empty and $statusCode is in [401, 403], skip the json message checks and perform the same saved-token swap and recursive call to $this->makeGitHubRequest($url, false) (preserving the finally block that restores $this->githubToken) so any 401/403 triggers a retry without token.public/assets/main.css (1)
4662-4689:⚠️ Potential issue | 🟡 MinorStylelint violations still present in generated utility blocks.
declaration-empty-line-beforeerrors are still triggered across these newly added utility declarations, so this CSS needs to be regenerated or auto-fixed before merge.Also applies to: 4853-4973, 5460-5626, 6398-6605, 6798-7071
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@public/assets/main.css` around lines 4662 - 4689, The generated CSS contains Stylelint violations for declaration-empty-line-before across utility rules (e.g., .border-amber-300, .border-blue-100, .border-blue-200, .border-blue-300, .border-blue-600, .border-blue-700, .border-cyan-200); regenerate or re-run your build step that produces public/assets/main.css (or run stylelint --fix) to insert the required empty lines before declarations and auto-fix similar blocks (also address the same violations in the other ranges noted: 4853-4973, 5460-5626, 6398-6605, 6798-7071) so the produced file passes declaration-empty-line-before rules before merging.vendor/tecnickcom/tcpdf/include/tcpdf_fonts.php (1)
1387-1387:⚠️ Potential issue | 🔴 Critical
checkSumAdjustmentis written to the wrongheadoffset.At Line 1387, the write uses
+4..+8instead of+8..+12, which overwritesfontRevisioninstead ofcheckSumAdjustment.🐛 Proposed fix
-$font = substr($font, 0, $table['head']['offset'] + $offset + 4).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + $offset + 8); +$font = substr($font, 0, $table['head']['offset'] + $offset + 8).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + $offset + 12);#!/bin/bash # Verify current write offsets used for checkSumAdjustment in subsetted TTF output. sed -n '1384,1388p' vendor/tecnickcom/tcpdf/include/tcpdf_fonts.php rg -n "checkSumAdjustment|\\$table\\['head'\\]\\['offset'\\] \\+ \\$offset \\+ (4|8|12)" vendor/tecnickcom/tcpdf/include/tcpdf_fonts.php🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/include/tcpdf_fonts.php` at line 1387, The current write to the TTF head table uses the wrong byte window and overwrites fontRevision; locate the assignment that builds $font (the line using $table['head']['offset'], $offset and $checkSumAdjustment) and change the substr boundaries so checkSumAdjustment is written at head offset + $offset + 8 .. +12 (i.e. slice up to offset+8, insert pack('N',$checkSumAdjustment), then append from offset+12) instead of the current +4..+8 to ensure checkSumAdjustment is written to the correct head field.vendor/laravel/serializable-closure/src/Support/ReflectionClosure.php (1)
1381-1387:⚠️ Potential issue | 🟠 MajorParameter parser can misclassify valid closures with default-call expressions.
At Line 1385, any
)in parameter parsing flips state to body. Defaults likefn($a = foo(), $b)can be parsed incorrectly, leading to false signature mismatch at Line 1404.#!/bin/bash set -euo pipefail FILE="vendor/laravel/serializable-closure/src/Support/ReflectionClosure.php" echo "=== verifyCandidateSignature excerpt ===" sed -n '1363,1433p' "$FILE" echo echo "=== Check for depth tracking inside verifyCandidateSignature ===" # Expectation: no depth variable currently present in method body. awk 'NR>=1363 && NR<=1433 {print}' "$FILE" | rg -n '\$depth|token === '\''\)'\'' && \$state === '\''params'\''' echo echo "If ')' transition exists without depth accounting, nested defaults can be misparsed."♻️ Proposed fix
protected function verifyCandidateSignature($candidate) { $code = $candidate['code']; $use = $candidate['use']; $isShortClosure = $candidate['isShortClosure']; @@ $tokens = token_get_all('<?php '.$code); $params = []; $vars = []; $state = 'start'; + $depth = 0; foreach ($tokens as $token) { if (! is_array($token)) { - if ($token === '(' && $state === 'start') { - $state = 'params'; - } elseif ($token === ')' && $state === 'params') { - $state = 'body'; + if ($token === '(') { + if ($state === 'start') { + $state = 'params'; + $depth = 1; + } elseif ($state === 'params') { + $depth++; + } + } elseif ($token === ')' && $state === 'params') { + $depth--; + if ($depth === 0) { + $state = 'body'; + } } continue; }Also applies to: 1404-1406
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/laravel/serializable-closure/src/Support/ReflectionClosure.php` around lines 1381 - 1387, The parameter parser in verifyCandidateSignature misclassifies closures when default parameter expressions contain parentheses because the tokens loop flips $state from 'params' to 'body' on any ')' regardless of nesting; update the tokens iteration in verifyCandidateSignature to track parenthesis nesting (e.g., introduce a $depth counter incrementing on '(' and decrementing on ')') and only transition from 'params' to 'body' when encountering a ')' while $depth is zero; adjust the checks that set $state ('(' -> 'params' and ')' -> 'body') to use the $depth guard so default-call expressions like fn($a = foo(), $b) are parsed correctly.
🧹 Nitpick comments (9)
vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php (1)
1-130: Reconsider committing vendor files to version control.This is a third-party dependency file from the
monolog/monologpackage. Standard PHP practice excludes thevendor/directory from version control (via.gitignore), managing dependencies solely throughcomposer.jsonandcomposer.lock. Committing vendor files leads to repository bloat, large diffs, and potential merge conflicts.If your project intentionally commits vendor files (some legacy or deployment-specific setups do), then these changes appear to be from a legitimate Monolog package update and are fine—they simply improve type documentation with
non-empty-stringannotations without altering behavior.</review_comment_end>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php` around lines 1 - 130, The PR accidentally includes a third-party vendor file (the Monolog class SlackWebhookHandler / Monolog\Handler\SlackWebhookHandler) which should not be committed; remove this file from version control (or revert the change) and ensure vendor/ is ignored by adding it to .gitignore, then restore dependencies via Composer (composer install) so the project uses the package from composer.lock; if your repo intentionally vendors dependencies, instead keep only the upstream package bump in composer.json/composer.lock and avoid raw edits to vendor files.vendor/tecnickcom/tcpdf/fonts/freefont-20120503/README (1)
33-33: Vendor file modified directly.These HTTPS URL updates are appropriate improvements, but modifying vendor files directly is discouraged. Changes will be overwritten on the next
composer update. Consider contributing these fixes upstream to the TCPDF repository instead.Also applies to: 111-111
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/fonts/freefont-20120503/README` at line 33, Revert direct edits to the vendor file README in freefont-20120503 (the modified vendor/tecnickcom/tcpdf/fonts/freefont-20120503/README) and instead either (a) submit the HTTPS URL correction as a PR to the upstream TCPDF repository, or (b) if you need the change applied in this project immediately, create a persistent patch via composer-patch or maintain a small fork of tecnickcom/tcpdf and update composer.json to require that fork; ensure the vendor README remains untouched in the committed tree and document the upstream PR or patch entry in the project changelog.vendor/tecnickcom/tcpdf/include/tcpdf_filters.php (1)
8-10: Vendor files should be managed via Composer, not committed directly.These TCPDF header updates (HTTPS license URL, copyright year 2026) appear to be from upstream version 6.11.0. Committing vendor files directly into the repository can lead to merge conflicts, makes updates error-prone, and bloats the repository.
Consider:
- Adding
vendor/to.gitignore- Using
composer install/composer updateto manage dependencies- Running
composer update tecnickcom/tcpdfto get upstream updates cleanly🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/include/tcpdf_filters.php` around lines 8 - 10, This change modifies upstream vendor files (tecnickcom/tcpdf) directly; instead revert these committed vendor edits, remove the vendor snapshot from the repo (undo or delete the tracked vendor/ files), add vendor/ to .gitignore, and manage the dependency via Composer by ensuring tecnickcom/tcpdf is declared in composer.json and running composer install/update (e.g., composer update tecnickcom/tcpdf or composer require tecnickcom/tcpdf) so the correct upstream 6.11.0 files are pulled and the repo no longer contains committed vendor code.vendor/tecnickcom/tcpdf/include/tcpdf_static.php (1)
495-495: Fix author docblock formatting typo.Line 495 reads
Klemen Vodopivec,2026 Nicola Asuni, which looks accidental and hurts readability.✏️ Proposed docblock fix
- * `@author` Klemen Vodopivec,2026 Nicola Asuni + * `@author` Klemen Vodopivec, Nicola Asuni (2026)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/include/tcpdf_static.php` at line 495, The author docblock on the tcpdf_static.php header contains a typo "Klemen Vodopivec,2026 Nicola Asuni"; update the `@author` line in the file's docblock to correctly separate authors and the year (e.g., "Klemen Vodopivec, 2004; Nicola Asuni, 2026" or "Klemen Vodopivec and Nicola Asuni, 2026") so the authors and year are properly formatted and readable; locate the `@author` tag in tcpdf_static.php and replace the malformed string accordingly.installer/database/data_de_DE.sql (1)
7-187: Prefer explicit column lists for seed inserts instead of positionalVALUES.Many statements rely on table column order (
INSERT ... VALUES (...)). That makes this seed brittle if schema changes (added/reordered columns) and can silently break installs/upgrades.Also applies to: 290-347
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@installer/database/data_de_DE.sql` around lines 7 - 187, The INSERTs into table `generi` use positional VALUES which is brittle; update every "INSERT IGNORE INTO `generi` VALUES (...)" (including the batch near lines 290-347) to use an explicit column list matching the table schema (for example: (id, name, description, created_at, updated_at, parent_id)) and then supply the VALUES in the same order, ensuring NULLs map to the correct columns; apply this change consistently across all INSERT IGNORE INTO `generi` statements in the file.app/Views/layout.php (1)
1436-1443: Add dialog semantics + focus lifecycle for the shortcuts modal.At Line 1606 and nearby markup, the modal should expose dialog semantics (
role="dialog",aria-modal,aria-labelledby).
At Line 1436-1443, opening/closing should also manage focus (focus close button on open, restore prior focus on close) for full keyboard/screen-reader usability.Suggested patch
- function openShortcutsModal() { - var modal = document.getElementById('shortcuts-modal'); - if (modal) modal.classList.remove('hidden'); - } - function closeShortcutsModal() { - var modal = document.getElementById('shortcuts-modal'); - if (modal) modal.classList.add('hidden'); - } + var lastFocusedElement = null; + function openShortcutsModal() { + var modal = document.getElementById('shortcuts-modal'); + if (!modal) return; + lastFocusedElement = document.activeElement; + modal.classList.remove('hidden'); + var close = document.getElementById('close-shortcuts'); + if (close) close.focus(); + } + function closeShortcutsModal() { + var modal = document.getElementById('shortcuts-modal'); + if (!modal) return; + modal.classList.add('hidden'); + if (lastFocusedElement && typeof lastFocusedElement.focus === 'function') { + lastFocusedElement.focus(); + } + } -<div id="shortcuts-modal" class="hidden fixed inset-0 bg-black/50 z-50 flex items-center justify-center"> +<div id="shortcuts-modal" role="dialog" aria-modal="true" aria-labelledby="shortcuts-title" class="hidden fixed inset-0 bg-black/50 z-50 flex items-center justify-center"> ... - <h3 class="font-semibold text-gray-900 flex items-center gap-2"> + <h3 id="shortcuts-title" class="font-semibold text-gray-900 flex items-center gap-2">Also applies to: 1606-1616
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Views/layout.php` around lines 1436 - 1443, Update the modal markup with dialog semantics by adding role="dialog", aria-modal="true", and aria-labelledby referencing the modal title element (add an id to the title) for the element with id 'shortcuts-modal'; in the JavaScript functions openShortcutsModal and closeShortcutsModal, implement focus lifecycle: when opening, save document.activeElement to a module-scoped variable (e.g., previousActiveElement), remove the 'hidden' class on 'shortcuts-modal', then move focus to the modal's close control (ensure you have a consistent identifier like id="shortcuts-close" or data attribute); when closing, add the 'hidden' class back and restore focus to previousActiveElement (null-check before calling focus), and ensure the saved variable is cleared; also wire Escape key handling within the modal to call closeShortcutsModal for keyboard users.vendor/tecnickcom/tcpdf/tcpdf.php (3)
4440-4440: Minor: unnecessary empty string concatenation.The trailing
''in the error message serves no purpose.♻️ Proposed fix
- $this->Error('Unknown font type: '.$type.''); + $this->Error('Unknown font type: '.$type);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/tcpdf.php` at line 4440, The error call unnecessarily concatenates an empty string; update the $this->Error invocation to remove the trailing ''. Replace "$this->Error('Unknown font type: '.$type.'');" with "$this->Error('Unknown font type: '.$type);" in the same method so the message remains identical but without the redundant concatenation.
23272-23276: Redundantfunction_exists()check.The
function_exists('xml_parser_free')guard is unnecessary sincexml_parser_free()exists in all PHP versions. ThePHP_VERSION_ID < 80000check alone is sufficient.♻️ Proposed simplification
- if (function_exists('xml_parser_free') && PHP_VERSION_ID < 80000) { + if (PHP_VERSION_ID < 80000) { xml_parser_free($parser); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/tcpdf.php` around lines 23272 - 23276, The code redundantly checks function_exists('xml_parser_free') before calling xml_parser_free($parser); remove the unnecessary function_exists() guard and retain only the PHP version check (PHP_VERSION_ID < 80000) around the xml_parser_free($parser) call so that xml_parser_free is invoked when running on PHP versions prior to 8.0; update the block that references xml_parser_free and PHP_VERSION_ID accordingly.
1-13: Modifying vendor files directly is not recommended.This file is managed by Composer and any local changes will be lost when running
composer update. Consider one of these alternatives:
- Fork the library and reference your fork in
composer.json- Submit upstream PRs for bug fixes to the TCPDF repository
- Use Composer patches (e.g.,
cweagans/composer-patches) to apply fixes declaratively- Override via inheritance where possible
If these modifications are intentional workarounds, document them and add a
composer.jsonscript or CI check to prevent accidental overwrites.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/tcpdf.php` around lines 1 - 13, The PR modifies a vendor-managed file (tcpdf.php) which will be overwritten by composer updates; instead of editing tcpdf.php directly, revert the vendor change and adopt one of the supported approaches: fork the TCPDF repo and point composer.json to your fork, submit an upstream PR to TCPDF, apply the change via a composer patch (e.g., cweagans/composer-patches) referenced in composer.json, or implement the behavior via subclassing/extension rather than editing tcpdf.php; if this edit is a deliberate temporary workaround, document it in the repo and add a composer.json script or CI check to detect/restore vendor modifications to avoid accidental overwrites.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/Controllers/LibriController.php`:
- Around line 2534-2555: The code currently treats an invalid or empty `ids`
input as "no filter" and exports all books; change to fail-closed: detect when
`ids` was provided (check original $params['ids'] presence) but parsing yields
an empty $selectedIds, and in that case ensure the export query produces no
results (e.g. set a flag or inject a guaranteed-false condition) or return a 400
error; update the parsing block around $idsParam/$selectedIds (and the later
IN(...) filter logic that uses $selectedIds) so that when $params contains 'ids'
but $selectedIds === [] you either add a no-op false clause to the query (so
IN(...) yields zero rows) or short-circuit and return an error response.
In `@app/Views/partials/scroll-to-top.php`:
- Around line 10-31: The scroll-to-top button (btn) is visually hidden via
opacity/pointer-events but can still receive keyboard focus; in the
window.scroll event handler (and when creating btn) toggle btn.tabIndex and
btn.setAttribute('aria-hidden') alongside opacity: set tabIndex = -1 and
aria-hidden = "true" when hidden, and tabIndex = 0 (or remove attribute) and
aria-hidden = "false" when visible; update the same logic that flips the visible
variable in the scroll listener so keyboard users cannot tab to the invisible
control and screen readers get correct state.
In `@scripts/manual-upgrade.php`:
- Around line 569-572: The manual-upgrade path uses a narrower $ignorableErrors
array which can abort harmless re-run migrations; update the $ignorableErrors
array in scripts/manual-upgrade.php (and its surrounding comment) to match the
same ignorable MySQL errno list used by the updater path so the same “already
exists / missing object” cases are treated idempotently (i.e., copy the exact
errno values from the updater implementation into $ignorableErrors), and keep
the existing $lastError handling unchanged.
In `@tests/full-test.spec.js`:
- Around line 598-600: finalTitle is interpolated into the SQL LIKE clause in
the dbQuery call (producing bookId), which allows quotes and LIKE wildcards
(%)/_ to break the query or cause false matches; fix by escaping user/scraped
input for LIKE (escape % and _ and backslash or quotes) or, better, convert the
dbQuery to a parameterized query that binds an escaped finalTitle with
surrounding % (e.g. bind value = `%${escapedFinalTitle}%`), and update both
occurrences that use finalTitle in dbQuery (the bookId lookup and the similar
call at lines ~709-710) to use the escape-and-bind approach or a helper like
escapeLike(finalTitle) before building the parameter.
- Around line 2173-2175: The assertion treats the SELECTed value stored in pages
incorrectly because dbQuery(`SELECT IFNULL(numero_pagine, 'NULL') ...`) can
yield the string 'NULL' which Number('NULL') => NaN; update the test to handle
'NULL' explicitly: fetch pages via dbQuery into pages, then if pages === 'NULL'
treat it as a valid null/zero case (or coerce to 0) before running the numeric
assertion; modify the expect to assert on the normalized numeric value (e.g.,
convert 'NULL' to 0 or check for pages === 'NULL' || Number(pages) <= 0) so both
0 and 'NULL' pass.
- Around line 2254-2255: The test currently asserts
expect(resp.status()).toBeGreaterThanOrEqual(400) which rejects valid redirects;
update the assertion to accept redirects and error statuses by changing the
check on resp.status() to toBeGreaterThanOrEqual(300) (or otherwise assert
resp.status() !== 200) so 3xx and 4xx/5xx responses pass; locate the assertion
using resp.status() in full-test.spec.js to apply the change.
- Around line 2206-2223: The test "19.2 XSS in book title is escaped on display"
currently uses window.__xss_triggered (never set) which gives a false-negative;
replace that check with Playwright's dialog interception by adding a dialog
listener or using page.waitForEvent('dialog') set up before navigating to
`${BASE}/libro/${bookId}`, fail the test if any dialog is received, and remove
the window.__xss_triggered evaluation; keep the existing page.goto,
page.waitForLoadState, and bodyHtml assertion but ensure the dialog listener is
registered prior to navigation so any alert() invocation is reliably detected.
- Around line 50-53: The dbQuery function currently always passes `-p${DB_PASS}`
(causing an interactive prompt when DB_PASS is empty) and never uses DB_HOST;
update dbQuery to build the mysql args array conditionally: only push
`-p${DB_PASS}` when DB_PASS is non-empty, always include `-h`, DB_HOST (so the
configured host is used), and still insert `-S`, DB_SOCKET when DB_SOCKET is set
(ensure socket ordering matches existing splice logic), then call
execFileSync('mysql', args, ...) as before; reference symbols: function dbQuery,
variables DB_PASS, DB_HOST, DB_SOCKET, and the execFileSync call.
In `@vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php`:
- Around line 137-142: The inode-change logic in StreamHandler is conflating two
concerns by setting $retrying when hasUrlInodeWasChanged() detects rotation;
instead, change the inode-rotation branch inside StreamHandler::write() so it
clears the inode tracking state (set $this->inodeUrl = null) and calls
$this->close(); $this->write($record); return; without toggling $this->retrying,
and remove any assignment of $this->retrying from hasUrlInodeWasChanged() so
that $this->retrying remains exclusively for write-failure retry logic (the
later check around !$this->retrying should therefore behave only for actual
write errors).
In `@vendor/tecnickcom/tcpdf/include/tcpdf_colors.php`:
- Line 10: Update the malformed copyright year text in the header string
"Copyright (C) 2002-2013 2026 Nicola Asuni - Tecnick.com LTD" to a single
continuous range by replacing "2002-2013 2026" with "2002-2026" so the header
reads "Copyright (C) 2002-2026 Nicola Asuni - Tecnick.com LTD".
---
Duplicate comments:
In `@app/Support/Updater.php`:
- Around line 587-601: The auth-retry in makeGitHubRequest is too restrictive
because it only retries when the response message contains specific text; change
it to mirror getAllReleases()/downloadUpdate by retrying on status codes alone:
when $allowAuthRetry is true, $this->githubToken is non-empty and $statusCode is
in [401, 403], skip the json message checks and perform the same saved-token
swap and recursive call to $this->makeGitHubRequest($url, false) (preserving the
finally block that restores $this->githubToken) so any 401/403 triggers a retry
without token.
In `@public/assets/main.css`:
- Around line 4662-4689: The generated CSS contains Stylelint violations for
declaration-empty-line-before across utility rules (e.g., .border-amber-300,
.border-blue-100, .border-blue-200, .border-blue-300, .border-blue-600,
.border-blue-700, .border-cyan-200); regenerate or re-run your build step that
produces public/assets/main.css (or run stylelint --fix) to insert the required
empty lines before declarations and auto-fix similar blocks (also address the
same violations in the other ranges noted: 4853-4973, 5460-5626, 6398-6605,
6798-7071) so the produced file passes declaration-empty-line-before rules
before merging.
In `@vendor/laravel/serializable-closure/src/Support/ReflectionClosure.php`:
- Around line 1381-1387: The parameter parser in verifyCandidateSignature
misclassifies closures when default parameter expressions contain parentheses
because the tokens loop flips $state from 'params' to 'body' on any ')'
regardless of nesting; update the tokens iteration in verifyCandidateSignature
to track parenthesis nesting (e.g., introduce a $depth counter incrementing on
'(' and decrementing on ')') and only transition from 'params' to 'body' when
encountering a ')' while $depth is zero; adjust the checks that set $state ('('
-> 'params' and ')' -> 'body') to use the $depth guard so default-call
expressions like fn($a = foo(), $b) are parsed correctly.
In `@vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php`:
- Around line 48-54: The constructor computes $this->nextRotation by calling
getNextRotation() before assigning $this->timezone, causing rotation cutoff to
ignore the configured timezone; fix it by assigning $this->timezone (the
DateTimeZone|null parameter) before calling $this->getNextRotation() in
__construct() (or alternatively change getNextRotation() to accept a timezone
argument), ensuring getNextRotation() uses the configured timezone when
calculating rotation boundaries and keeping filename dating in
setFilenameFormat()/getDatedFilename() consistent.
In `@vendor/tecnickcom/tcpdf/include/tcpdf_fonts.php`:
- Line 1387: The current write to the TTF head table uses the wrong byte window
and overwrites fontRevision; locate the assignment that builds $font (the line
using $table['head']['offset'], $offset and $checkSumAdjustment) and change the
substr boundaries so checkSumAdjustment is written at head offset + $offset + 8
.. +12 (i.e. slice up to offset+8, insert pack('N',$checkSumAdjustment), then
append from offset+12) instead of the current +4..+8 to ensure
checkSumAdjustment is written to the correct head field.
In `@vendor/tecnickcom/tcpdf/tcpdf_autoconfig.php`:
- Line 157: The PDF header constant PDF_HEADER_STRING contains a typo "by2026"
that will appear in generated PDFs; update the constant definition for
PDF_HEADER_STRING (the define call) to use "by" instead of "by2026" so the
string reads "by Nicola Asuni - Tecnick.com\nwww.tcpdf.org". Ensure you update
the define('PDF_HEADER_STRING', ...) entry where it's declared.
---
Nitpick comments:
In `@app/Views/layout.php`:
- Around line 1436-1443: Update the modal markup with dialog semantics by adding
role="dialog", aria-modal="true", and aria-labelledby referencing the modal
title element (add an id to the title) for the element with id
'shortcuts-modal'; in the JavaScript functions openShortcutsModal and
closeShortcutsModal, implement focus lifecycle: when opening, save
document.activeElement to a module-scoped variable (e.g.,
previousActiveElement), remove the 'hidden' class on 'shortcuts-modal', then
move focus to the modal's close control (ensure you have a consistent identifier
like id="shortcuts-close" or data attribute); when closing, add the 'hidden'
class back and restore focus to previousActiveElement (null-check before calling
focus), and ensure the saved variable is cleared; also wire Escape key handling
within the modal to call closeShortcutsModal for keyboard users.
In `@installer/database/data_de_DE.sql`:
- Around line 7-187: The INSERTs into table `generi` use positional VALUES which
is brittle; update every "INSERT IGNORE INTO `generi` VALUES (...)" (including
the batch near lines 290-347) to use an explicit column list matching the table
schema (for example: (id, name, description, created_at, updated_at, parent_id))
and then supply the VALUES in the same order, ensuring NULLs map to the correct
columns; apply this change consistently across all INSERT IGNORE INTO `generi`
statements in the file.
In `@vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php`:
- Around line 1-130: The PR accidentally includes a third-party vendor file (the
Monolog class SlackWebhookHandler / Monolog\Handler\SlackWebhookHandler) which
should not be committed; remove this file from version control (or revert the
change) and ensure vendor/ is ignored by adding it to .gitignore, then restore
dependencies via Composer (composer install) so the project uses the package
from composer.lock; if your repo intentionally vendors dependencies, instead
keep only the upstream package bump in composer.json/composer.lock and avoid raw
edits to vendor files.
In `@vendor/tecnickcom/tcpdf/fonts/freefont-20120503/README`:
- Line 33: Revert direct edits to the vendor file README in freefont-20120503
(the modified vendor/tecnickcom/tcpdf/fonts/freefont-20120503/README) and
instead either (a) submit the HTTPS URL correction as a PR to the upstream TCPDF
repository, or (b) if you need the change applied in this project immediately,
create a persistent patch via composer-patch or maintain a small fork of
tecnickcom/tcpdf and update composer.json to require that fork; ensure the
vendor README remains untouched in the committed tree and document the upstream
PR or patch entry in the project changelog.
In `@vendor/tecnickcom/tcpdf/include/tcpdf_filters.php`:
- Around line 8-10: This change modifies upstream vendor files
(tecnickcom/tcpdf) directly; instead revert these committed vendor edits, remove
the vendor snapshot from the repo (undo or delete the tracked vendor/ files),
add vendor/ to .gitignore, and manage the dependency via Composer by ensuring
tecnickcom/tcpdf is declared in composer.json and running composer
install/update (e.g., composer update tecnickcom/tcpdf or composer require
tecnickcom/tcpdf) so the correct upstream 6.11.0 files are pulled and the repo
no longer contains committed vendor code.
In `@vendor/tecnickcom/tcpdf/include/tcpdf_static.php`:
- Line 495: The author docblock on the tcpdf_static.php header contains a typo
"Klemen Vodopivec,2026 Nicola Asuni"; update the `@author` line in the file's
docblock to correctly separate authors and the year (e.g., "Klemen Vodopivec,
2004; Nicola Asuni, 2026" or "Klemen Vodopivec and Nicola Asuni, 2026") so the
authors and year are properly formatted and readable; locate the `@author` tag in
tcpdf_static.php and replace the malformed string accordingly.
In `@vendor/tecnickcom/tcpdf/tcpdf.php`:
- Line 4440: The error call unnecessarily concatenates an empty string; update
the $this->Error invocation to remove the trailing ''. Replace
"$this->Error('Unknown font type: '.$type.'');" with "$this->Error('Unknown font
type: '.$type);" in the same method so the message remains identical but without
the redundant concatenation.
- Around line 23272-23276: The code redundantly checks
function_exists('xml_parser_free') before calling xml_parser_free($parser);
remove the unnecessary function_exists() guard and retain only the PHP version
check (PHP_VERSION_ID < 80000) around the xml_parser_free($parser) call so that
xml_parser_free is invoked when running on PHP versions prior to 8.0; update the
block that references xml_parser_free and PHP_VERSION_ID accordingly.
- Around line 1-13: The PR modifies a vendor-managed file (tcpdf.php) which will
be overwritten by composer updates; instead of editing tcpdf.php directly,
revert the vendor change and adopt one of the supported approaches: fork the
TCPDF repo and point composer.json to your fork, submit an upstream PR to TCPDF,
apply the change via a composer patch (e.g., cweagans/composer-patches)
referenced in composer.json, or implement the behavior via subclassing/extension
rather than editing tcpdf.php; if this edit is a deliberate temporary
workaround, document it in the repo and add a composer.json script or CI check
to detect/restore vendor modifications to avoid accidental overwrites.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
composer.lockis excluded by!**/*.lock
📒 Files selected for processing (110)
.htaccessREADME.mdapp/Controllers/FrontendController.phpapp/Controllers/LibriController.phpapp/Controllers/UpdateController.phpapp/Models/BookRepository.phpapp/Routes/web.phpapp/Support/I18n.phpapp/Support/Updater.phpapp/Views/admin/updates.phpapp/Views/autori/index.phpapp/Views/editori/index.phpapp/Views/events/index.phpapp/Views/frontend/event-detail.phpapp/Views/frontend/events.phpapp/Views/frontend/home-sections/events.phpapp/Views/frontend/home-sections/genre_carousel.phpapp/Views/frontend/home.phpapp/Views/frontend/layout.phpapp/Views/layout.phpapp/Views/libri/index.phpapp/Views/libri/partials/book_form.phpapp/Views/libri/scheda_libro.phpapp/Views/partials/scroll-to-top.phpapp/Views/prestiti/index.phpapp/Views/utenti/index.phpfrontend/js/vendor.jsinstaller/classes/Installer.phpinstaller/database/data_de_DE.sqlinstaller/database/data_en_US.sqlinstaller/database/data_it_IT.sqlinstaller/index.phpinstaller/steps/step0.phplocale/de_DE.jsonlocale/routes_de_DE.jsonpublic/assets/main.csspublic/assets/vendor.bundle.jsscripts/.user.iniscripts/create-release.shscripts/manual-update.phpscripts/manual-upgrade.phptests/full-test.spec.jstests/genre-bugs.spec.jsvendor/composer/autoload_classmap.phpvendor/composer/autoload_files.phpvendor/composer/autoload_static.phpvendor/composer/installed.jsonvendor/composer/installed.phpvendor/graham-campbell/result-type/composer.jsonvendor/laravel/serializable-closure/composer.jsonvendor/laravel/serializable-closure/src/Serializers/Native.phpvendor/laravel/serializable-closure/src/Support/ReflectionClosure.phpvendor/monolog/monolog/README.mdvendor/monolog/monolog/composer.jsonvendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.phpvendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.phpvendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.phpvendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.phpvendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.phpvendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.phpvendor/monolog/monolog/src/Monolog/Handler/CubeHandler.phpvendor/monolog/monolog/src/Monolog/Handler/Curl/Util.phpvendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.phpvendor/monolog/monolog/src/Monolog/Handler/Handler.phpvendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.phpvendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.phpvendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.phpvendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.phpvendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.phpvendor/monolog/monolog/src/Monolog/Handler/StreamHandler.phpvendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.phpvendor/monolog/monolog/src/Monolog/Handler/TestHandler.phpvendor/slim/csrf/composer.jsonvendor/slim/psr7/composer.jsonvendor/slim/psr7/src/Cookies.phpvendor/slim/slim/MAINTAINERS.mdvendor/slim/slim/Slim/App.phpvendor/slim/slim/composer.jsonvendor/slim/slim/psalm.xmlvendor/tecnickcom/tcpdf/CHANGELOG.TXTvendor/tecnickcom/tcpdf/LICENSE.TXTvendor/tecnickcom/tcpdf/README.mdvendor/tecnickcom/tcpdf/VERSIONvendor/tecnickcom/tcpdf/composer.jsonvendor/tecnickcom/tcpdf/config/tcpdf_config.phpvendor/tecnickcom/tcpdf/fonts/ae_fonts_2.0/READMEvendor/tecnickcom/tcpdf/fonts/freefont-20100919/COPYINGvendor/tecnickcom/tcpdf/fonts/freefont-20100919/CREDITSvendor/tecnickcom/tcpdf/fonts/freefont-20120503/COPYINGvendor/tecnickcom/tcpdf/fonts/freefont-20120503/CREDITSvendor/tecnickcom/tcpdf/fonts/freefont-20120503/READMEvendor/tecnickcom/tcpdf/fonts/freefont-20120503/TROUBLESHOOTINGvendor/tecnickcom/tcpdf/include/barcodes/datamatrix.phpvendor/tecnickcom/tcpdf/include/barcodes/pdf417.phpvendor/tecnickcom/tcpdf/include/barcodes/qrcode.phpvendor/tecnickcom/tcpdf/include/tcpdf_colors.phpvendor/tecnickcom/tcpdf/include/tcpdf_filters.phpvendor/tecnickcom/tcpdf/include/tcpdf_font_data.phpvendor/tecnickcom/tcpdf/include/tcpdf_fonts.phpvendor/tecnickcom/tcpdf/include/tcpdf_images.phpvendor/tecnickcom/tcpdf/include/tcpdf_static.phpvendor/tecnickcom/tcpdf/tcpdf.phpvendor/tecnickcom/tcpdf/tcpdf_autoconfig.phpvendor/tecnickcom/tcpdf/tcpdf_barcodes_1d.phpvendor/tecnickcom/tcpdf/tcpdf_barcodes_2d.phpvendor/tecnickcom/tcpdf/tools/tcpdf_addfont.phpvendor/vlucas/phpdotenv/composer.jsonvendor/vlucas/phpdotenv/src/Parser/EntryParser.phpvendor/vlucas/phpdotenv/src/Repository/Adapter/EnvConstAdapter.phpvendor/vlucas/phpdotenv/src/Repository/Adapter/ServerConstAdapter.php
💤 Files with no reviewable changes (7)
- vendor/vlucas/phpdotenv/src/Parser/EntryParser.php
- vendor/vlucas/phpdotenv/src/Repository/Adapter/ServerConstAdapter.php
- vendor/slim/slim/psalm.xml
- vendor/slim/slim/MAINTAINERS.md
- scripts/manual-update.php
- vendor/vlucas/phpdotenv/src/Repository/Adapter/EnvConstAdapter.php
- vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php
| if ($this->hasUrlInodeWasChanged()) { | ||
| $this->close(); | ||
| $this->write($record); | ||
|
|
||
| return; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php"
echo "Inspecting retry-related control flow..."
rg -n -C4 '\$retrying|hasUrlInodeWasChanged|inodeUrl' "$FILE"
echo
echo "Focused excerpt: write() method"
sed -n '130,210p' "$FILE"
echo
echo "Focused excerpt: inode helpers"
sed -n '258,282p' "$FILE"Repository: fabiodalez-dev/Pinakes
Length of output: 5054
Decouple inode-reopen state from write-retry state.
The $retrying flag is reused for two distinct purposes: inode-rotation detection and write-failure retry control. In hasUrlInodeWasChanged(), when an inode change is detected, $retrying is set to true. However, when the recursive write() call that follows (line 139) encounters a write error, the check at line 183 (if (!$this->retrying ...)) skips the write-failure retry because $retrying was already set by the inode handler. This suppresses the intended one-time retry after an inode-triggered reopen.
The fix decouples these concerns by:
- Resetting
$inodeUrl = null;in the inode-change handler (line 138), which naturally prevents re-entry intohasUrlInodeWasChanged()without needing$retrying - Removing
$retryingfrom the inode detection logic (line 272) - Allowing
$retryingto be used exclusively for write-failure retry control
Proposed fix
protected function write(LogRecord $record): void
{
if ($this->hasUrlInodeWasChanged()) {
+ $this->inodeUrl = null;
$this->close();
$this->write($record);
return;
}
@@
private function hasUrlInodeWasChanged(): bool
{
- if ($this->inodeUrl === null || $this->retrying || $this->inodeUrl === $this->getInodeFromUrl()) {
+ if ($this->inodeUrl === null || $this->inodeUrl === $this->getInodeFromUrl()) {
return false;
}
-
- $this->retrying = true;
return true;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php` around lines
137 - 142, The inode-change logic in StreamHandler is conflating two concerns by
setting $retrying when hasUrlInodeWasChanged() detects rotation; instead, change
the inode-rotation branch inside StreamHandler::write() so it clears the inode
tracking state (set $this->inodeUrl = null) and calls $this->close();
$this->write($record); return; without toggling $this->retrying, and remove any
assignment of $this->retrying from hasUrlInodeWasChanged() so that
$this->retrying remains exclusively for write-failure retry logic (the later
check around !$this->retrying should therefore behave only for actual write
errors).
| // License : GNU-LGPL v3 (https://www.gnu.org/copyleft/lesser.html) | ||
| // ------------------------------------------------------------------- | ||
| // Copyright (C) 2002-2013 Nicola Asuni - Tecnick.com LTD | ||
| // Copyright (C) 2002-2013 2026 Nicola Asuni - Tecnick.com LTD |
There was a problem hiding this comment.
Malformed copyright year range.
The copyright line 2002-2013 2026 is malformed. It should be 2002-2026 (single continuous range).
Suggested fix
-// Copyright (C) 2002-2013 2026 Nicola Asuni - Tecnick.com LTD
+// Copyright (C) 2002-2026 Nicola Asuni - Tecnick.com LTD🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vendor/tecnickcom/tcpdf/include/tcpdf_colors.php` at line 10, Update the
malformed copyright year text in the header string "Copyright (C) 2002-2013 2026
Nicola Asuni - Tecnick.com LTD" to a single continuous range by replacing
"2002-2013 2026" with "2002-2026" so the header reads "Copyright (C) 2002-2026
Nicola Asuni - Tecnick.com LTD".
- Updater: simplify auth retry to status-code-only (match other paths) - LibriController: fail-closed when ids param yields no valid IDs - scroll-to-top: add aria-hidden/tabIndex toggling for a11y - layout shortcuts modal: add role=dialog, aria-modal, focus lifecycle - manual-upgrade: align ignorable MySQL errors with Updater (add 1022/1826/1146) - tests: fix dbQuery to handle empty password and use DB_HOST - tests: add escapeSqlLike helper for LIKE queries - tests: fix numero_pagine NULL assertion (handle 'NULL' string) - tests: use Playwright dialog interception for XSS test - tests: accept redirects (3xx) for soft-deleted book assertion
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
installer/classes/Installer.php (1)
1023-1036:⚠️ Potential issue | 🟡 MinorIPv6 host parsing can still produce an invalid canonical URL.
At Line 1023, splitting
$hostwithexplode(':', $host, 2)breaks bracketed IPv6 literals (for example[2001:db8::1]:8080), resulting in malformedAPP_CANONICAL_URL.🔧 Suggested fix
- if (str_contains($host, ':')) { - [$hostOnly, $portPart] = explode(':', $host, 2); - $host = $hostOnly; - $port = is_numeric($portPart) ? (int)$portPart : null; + if (preg_match('/^\[(.+)\](?::(\d+))?$/', $host, $matches)) { + // Bracketed IPv6 literal, optionally with port + $host = '[' . $matches[1] . ']'; + $port = isset($matches[2]) ? (int) $matches[2] : null; + } elseif (substr_count($host, ':') === 1) { + // hostname:port or IPv4:port + [$hostOnly, $portPart] = explode(':', $host, 2); + $host = $hostOnly; + $port = is_numeric($portPart) ? (int) $portPart : null; } elseif (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) { $port = (int)$_SERVER['HTTP_X_FORWARDED_PORT']; } elseif (isset($_SERVER['SERVER_PORT']) && is_numeric((string)$_SERVER['SERVER_PORT'])) { $port = (int)$_SERVER['SERVER_PORT']; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@installer/classes/Installer.php` around lines 1023 - 1036, The current parsing in Installer.php uses str_contains($host, ':') and explode(':', $host, 2) which breaks bracketed IPv6 literals like [2001:db8::1]:8080; update the logic around $host/$port extraction to first detect bracketed IPv6 (host starts with '[' and contains a ']') and if so extract the text inside brackets as $hostOnly and anything after the closing ']' (after a colon) as $portPart, otherwise split on the last colon (use strrpos semantics instead of explode) to avoid breaking IPv6 addresses without brackets; then set $host and $port as before and keep the later base and defaultPorts handling unchanged.
♻️ Duplicate comments (9)
vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php (1)
137-142:⚠️ Potential issue | 🟠 MajorDecouple inode-rotation state from write-retry state.
At Line 272 and Line 276,
$retryingis being used for inode-rotation control, but Line 187 uses the same flag to allow one write-failure retry. After an inode-triggered reopen, a subsequent write error can skip the intended retry path.Suggested fix
protected function write(LogRecord $record): void { if ($this->hasUrlInodeWasChanged()) { + $this->inodeUrl = null; $this->close(); $this->write($record); return; } @@ private function hasUrlInodeWasChanged(): bool { - if ($this->inodeUrl === null || $this->retrying || $this->inodeUrl === $this->getInodeFromUrl()) { + if ($this->inodeUrl === null || $this->inodeUrl === $this->getInodeFromUrl()) { return false; } - - $this->retrying = true; return true; }#!/bin/bash set -euo pipefail FILE="vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php" echo "Inspect retry/inode interactions:" rg -n -C3 '\$retrying|hasUrlInodeWasChanged|inodeUrl' "$FILE" echo echo "write() focus:" sed -n '135,205p' "$FILE" echo echo "inode helpers focus:" sed -n '259,279p' "$FILE"Also applies to: 272-277
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php` around lines 137 - 142, The inode-rotation logic in write() is reusing the $retrying flag so an inode-triggered reopen can consume the one-time write-retry; introduce a separate boolean (e.g. $inodeReopened or $reopenedForInode) and use it for the hasUrlInodeWasChanged() / close() path instead of mutating or relying on $retrying, or explicitly restore/reset $retrying after the inode reopen, so the original write-failure retry behavior remains intact; update references in write(), hasUrlInodeWasChanged() handling, and any code paths that check $retrying to use the new flag where appropriate.vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php (1)
53-54:⚠️ Potential issue | 🟠 MajorAlign rotation-boundary timezone with configured filename timezone.
Line 53 computes
$this->nextRotationbefore Line 54 sets$this->timezone, and Line 226-232 still uses default-timezoneDateTimeImmutableinstances. This can rotate on a different boundary than the filename date (Line 183).Proposed fix
- $this->nextRotation = $this->getNextRotation(); - $this->timezone = $timezone; + $this->timezone = $timezone; + $this->nextRotation = $this->getNextRotation();protected function getNextRotation(): \DateTimeImmutable { + $now = new \DateTimeImmutable('now', $this->timezone); + return match (str_replace(['/','_','.'], '-', $this->dateFormat)) { - self::FILE_PER_MONTH => (new \DateTimeImmutable('first day of next month'))->setTime(0, 0, 0), - self::FILE_PER_YEAR => (new \DateTimeImmutable('first day of January next year'))->setTime(0, 0, 0), - default => (new \DateTimeImmutable('tomorrow'))->setTime(0, 0, 0), + self::FILE_PER_MONTH => $now->modify('first day of next month')->setTime(0, 0, 0), + self::FILE_PER_YEAR => $now->modify('first day of January next year')->setTime(0, 0, 0), + default => $now->modify('tomorrow')->setTime(0, 0, 0), }; }#!/bin/bash set -euo pipefail file="vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php" echo "Constructor ordering (expect timezone assignment before getNextRotation):" nl -ba "$file" | sed -n '48,58p' echo echo "Rotation boundary calculation (expect DateTimeImmutable initialized with configured timezone):" nl -ba "$file" | sed -n '226,234p'Also applies to: 127-127, 226-232
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php` around lines 53 - 54, The constructor in RotatingFileHandler sets $this->nextRotation before assigning $this->timezone which can cause rotation boundaries to use the default timezone; move the assignment of $this->timezone so it occurs before calling $this->getNextRotation() (i.e., ensure $this->timezone is set prior to computing $this->nextRotation in the constructor). Additionally, update the rotation-boundary calculations inside getNextRotation() (and any DateTimeImmutable instantiations used for rotation boundary or filename generation) to construct DateTimeImmutable objects with $this->timezone (e.g., new DateTimeImmutable('now', $this->timezone)) so the rotation boundary aligns with the configured filename timezone.vendor/tecnickcom/tcpdf/include/tcpdf_colors.php (1)
10-10:⚠️ Potential issue | 🟡 MinorMalformed copyright year range remains unresolved.
Line 10 still uses
2002-2013 2026; this should be a single continuous range (e.g.,2002-2026).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/include/tcpdf_colors.php` at line 10, Update the malformed copyright year range in the header string "Copyright (C) 2002-2013 2026 Nicola Asuni - Tecnick.com LTD" to a single continuous range (for example "Copyright (C) 2002-2026 Nicola Asuni - Tecnick.com LTD") in tcpdf_colors.php so the year sequence is correct and no duplicate years remain.vendor/tecnickcom/tcpdf/include/tcpdf_images.php (1)
277-277:⚠️ Potential issue | 🟡 MinorFix typo in PNG parser comments (
Unknownn→Unknown).Line 277, Line 282, and Line 334 still contain
Unknownn(doublen) in comments.✏️ Proposed fix
- // Unknownn compression method + // Unknown compression method ... - // Unknownn filter method + // Unknown filter method ... - // Unknownn filter method + // Unknown filter methodAlso applies to: 282-282, 334-334
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/include/tcpdf_images.php` at line 277, Fix the typo "Unknownn" → "Unknown" in the PNG parser comments in tcpdf_images.php: locate the PNG parsing section (the PNG parse function / PNG parser comment blocks) and replace all occurrences of the misspelled "Unknownn" (e.g., in comment strings like "Unknownn compression method") with "Unknown" so the comments read correctly; ensure the three occurrences noted in the review are corrected.app/Views/partials/scroll-to-top.php (1)
5-6:⚠️ Potential issue | 🟠 MajorEscape translated text before embedding it in JavaScript.
Line 5 and Line 6 inject localized text directly into single-quoted JS strings. A translation containing quotes or
</script>can break script context.🛡️ Proposed fix
- btn.setAttribute('aria-label', '<?= __('Torna su') ?>'); - btn.setAttribute('title', '<?= __('Torna su') ?>'); + var scrollToTopLabel = <?= json_encode(__('Torna su'), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) ?>; + btn.setAttribute('aria-label', scrollToTopLabel); + btn.setAttribute('title', scrollToTopLabel);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Views/partials/scroll-to-top.php` around lines 5 - 6, The translated string is injected directly into JS strings via btn.setAttribute('aria-label', '<?= __('Torna su') ?>') and btn.setAttribute('title', '<?= __('Torna su') ?>'), which can break the script if the translation contains quotes or script-close sequences; fix by encoding/escaping the translation for JavaScript output (emit the translation as a JSON-safe JS string using PHP's json_encode or an equivalent JS-escaping helper) so the second argument to btn.setAttribute receives a properly escaped string.vendor/tecnickcom/tcpdf/include/tcpdf_fonts.php (1)
1387-1387:⚠️ Potential issue | 🔴 CriticalFix
checkSumAdjustmentwrite offset in theheadtable.Line 1387 currently writes at
+4..+8;checkSumAdjustmentmust be written at+8..+12. The current slicing can corrupt subsetted TTF metadata.🔧 Proposed fix
-$font = substr($font, 0, $table['head']['offset'] + $offset + 4).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + $offset + 8); +$font = substr($font, 0, $table['head']['offset'] + $offset + 8).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + $offset + 12);According to the TrueType/OpenType `head` table specification, what byte offset stores `checkSumAdjustment`?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/include/tcpdf_fonts.php` at line 1387, The head table's checkSumAdjustment is being written at bytes +4..+8 but per the TrueType/OpenType spec it must be at +8..+12; update the write in the code that manipulates $font (the expression using $table['head']['offset'], $offset and $checkSumAdjustment) so it inserts pack('N', $checkSumAdjustment) starting at $table['head']['offset'] + $offset + 8 and continues to +12 (i.e., use substr boundaries +8 and +12 instead of +4 and +8) to avoid corrupting subsetted TTF metadata.tests/full-test.spec.js (1)
2269-2273:⚠️ Potential issue | 🟡 MinorGuard
gotoresponse before reading status.If
page.gotoreturnsnull,resp.status()throws and hides the actual assertion intent.🛠️ Suggested patch
- expect(resp.status()).not.toBe(200); + const status = resp?.status() ?? 0; + expect(status).not.toBe(200);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/full-test.spec.js` around lines 2269 - 2273, Guard the result of page.goto before calling resp.status() to avoid errors when it returns null: check the returned value from page.goto (resp) and treat a null response as a non-200 result (or extract status via a safe check), then perform the assertion using that guarded status; update the code around page.goto / resp.status() so it uses a conditional check on resp (or a safe accessor) before calling status().app/Views/libri/partials/book_form.php (1)
1378-1417:⚠️ Potential issue | 🟠 MajorDo not override Choices.js private
_onEnterKeyAPI.This is still relying on an internal method and remains version-fragile (already flagged in prior review, still unresolved).
🔧 Suggested direction (replace private API patch)
- if (typeof authorsChoice._onEnterKey === 'function') { - const originalOnEnterKey = authorsChoice._onEnterKey.bind(authorsChoice); - authorsChoice._onEnterKey = function(event, hasActiveDropdown) { - ... - return originalOnEnterKey(event, hasActiveDropdown); - }; - } else { - console.warn('Choices.js _onEnterKey not available; author creation on Enter disabled.'); - } + const handleAuthorEnter = (event) => { + if (event.key !== 'Enter' || !internalInput) return; + const inputValue = internalInput.value.trim(); + if (!inputValue) return; + + const dd = wrapper ? wrapper.querySelector('.choices__list--dropdown') : null; + const highlighted = dd ? dd.querySelector('.choices__item--selectable.is-highlighted') : null; + + if (!highlighted) { + event.preventDefault(); + event.stopPropagation(); + createAuthorFromInputWithValue(inputValue); + return; + } + + const highlightedText = (highlighted.textContent || '').trim().toLowerCase(); + const currentText = inputValue.toLowerCase(); + if (highlightedText !== currentText && !highlightedText.startsWith(currentText)) { + event.preventDefault(); + event.stopPropagation(); + createAuthorFromInputWithValue(inputValue); + } + }; + + document.addEventListener('keydown', handleAuthorEnter, true);Use this script to verify where private Choices.js APIs are patched and what version is pinned:
#!/bin/bash set -euo pipefail echo "== Private Choices.js API usages ==" rg -n "_onEnterKey" app/Views/libri/partials/book_form.php echo echo "== choices.js version declarations ==" python - <<'PY' import json, pathlib for p in pathlib.Path('.').rglob('package.json'): try: data = json.loads(p.read_text()) except Exception: continue deps = {} deps.update(data.get('dependencies', {})) deps.update(data.get('devDependencies', {})) if 'choices.js' in deps: print(f"{p}: {deps['choices.js']}") PY🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Views/libri/partials/book_form.php` around lines 1378 - 1417, You are overriding a private Choices.js method (authorsChoice._onEnterKey) which is fragile; instead attach a stable public handler: listen to the actual input element (internalInput) keydown/keypress or Choices public events (e.g. 'addItem' / 'choice' / 'search') and implement the create-on-Enter logic there by detecting Enter, reading internalInput.value, checking the dropdown highlighted/choices via public DOM queries (wrapper / .choices__list--dropdown / .choices__item--selectable) and calling createAuthorFromInputWithValue when appropriate; remove the override of authorsChoice._onEnterKey and ensure the new handler prevents default and stops propagation only when creating a new author.vendor/tecnickcom/tcpdf/tcpdf.php (1)
23733-23737:⚠️ Potential issue | 🟠 MajorSVG path parameter splitting retains delimiters due to capturing group in preg_split.
Line 23734 uses a capturing group
'/([\,\s]+)/si'which includes the matched delimiters (commas, spaces) as separate array elements in the result. The subsequentarray_filterremoves empty strings but leaves delimiters intact, corrupting SVG coordinate parsing.Use a non-capturing group with
PREG_SPLIT_NO_EMPTYinstead:Proposed fix
- $rawparams = preg_split('/([\,\s]+)/si', trim($val[2])); - $rawparams = array_filter($rawparams, function($p) { - return trim($p) != ''; - }); + $rawparams = preg_split('/[\,\s]+/si', trim($val[2]), -1, PREG_SPLIT_NO_EMPTY);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/tcpdf.php` around lines 23733 - 23737, The preg_split call that builds $rawparams is using a capturing group '/([\,\s]+)/si' which causes delimiters to appear in the result; update the preg_split invocation in the code that populates $rawparams to use a non‑capturing group and the PREG_SPLIT_NO_EMPTY flag (e.g. '/(?:[,\s]+)/si' with PREG_SPLIT_NO_EMPTY) so delimiters are not returned and you can remove the subsequent array_filter step that tries to drop empty strings.
🧹 Nitpick comments (2)
installer/database/data_de_DE.sql (1)
189-191: Deduplicatecms.events_page_enabledupsert to keep one source of truth.Line 190 and Line 282 seed the same setting key; the later write silently overrides the first. Keeping a single insert improves maintainability.
♻️ Suggested cleanup
-INSERT INTO `system_settings` (`category`, `setting_key`, `setting_value`, `description`) VALUES -('cms', 'events_page_enabled', '1', 'Veranstaltungsseite im Frontend aktivieren oder deaktivieren') -ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value), description = VALUES(description);Also applies to: 281-286
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@installer/database/data_de_DE.sql` around lines 189 - 191, The seed contains duplicate upserts for the same setting key 'cms.events_page_enabled' which causes the later INSERT to silently override the earlier one; remove the redundant INSERT/ON DUPLICATE KEY UPDATE block so there is a single canonical upsert for 'cms.events_page_enabled' (keep the preferred values/description and delete the other occurrence), ensuring only one source of truth for that setting in the SQL seed.vendor/tecnickcom/tcpdf/tcpdf.php (1)
4-6: Consider contributing fixes upstream or maintaining patches separately.This vendor file contains local modifications that may be overwritten when updating via Composer. If these changes fix real issues, consider:
- Contributing the fixes upstream to tecnickcom/TCPDF
- Using a patch file applied during build (e.g.,
cweagans/composer-patches)- Forking the repository and requiring your fork in
composer.json🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/tcpdf.php` around lines 4 - 6, This vendor file (tcpdf.php) has local modifications that will be lost on Composer updates; stop editing vendor/tecnickcom/tcpdf/tcpdf.php directly and instead either submit the fixes upstream to tecnickcom/TCPDF (open a PR referencing the exact changes in tcpdf.php), maintain the fixes as a project patch applied during install (e.g., add a patch entry using cweagans/composer-patches referencing the modified tcpdf.php hunks), or fork the TCPDF repo and require your fork in composer.json so the changes to tcpdf.php persist across updates.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/Controllers/FrontendController.php`:
- Around line 1305-1307: The prepare() failure for the SQL statement (e.g. when
$descStmt === false) should be treated as a server error, not a missing
resource: replace the current return $this->render404($response) calls (seen
around the $descStmt/$metaStmt/$topicStmt checks) with server-error handling —
either return $this->render500($response) if a render500 helper exists, or
throw/return an appropriate 500-level exception/response after logging the PDO
error (use the PDO connection's errorInfo() or exception message) so failures in
prepare() are surfaced as 5xx and logged for debugging.
In `@app/Controllers/LibriController.php`:
- Line 640: The current coercion for $fields['numero_pagine'] casts any
non-empty string (e.g. "abc") to int and then to 1; change the logic to first
check that the value is numeric (use is_numeric or filter_var with
FILTER_VALIDATE_INT) and only then cast and apply max(1, (int)...); if the input
is empty or not a valid number, set the field to null (do not coerce invalid
strings to 1). Apply the same fix to the other occurrence referenced around the
second location (the similar $fields['numero_pagine'] handling at the other
spot).
In `@scripts/manual-upgrade.php`:
- Around line 260-268: Add brute-force protection around the POST password check
by tracking and enforcing attempt limits in session or persistent store: before
validating UPGRADE_PASSWORD, check a counter like $_SESSION['upgrade_attempts']
and a timestamp like $_SESSION['upgrade_last_attempt']; if attempts exceed a
threshold (e.g., 5) within a time window (e.g., 15 minutes) set $error to a
lockout message and refuse further checks; on each failed hash_equals increment
the counter and update the timestamp, and on successful authentication reset the
counter and timestamp before calling session_regenerate_id(true) and setting
$_SESSION['upgrade_auth']; also consider adding a short delay or logging remote
IP for repeated failures.
In `@tests/full-test.spec.js`:
- Around line 2002-2009: The test is passing a genre id to the catalog filter
but the app compares by genre name (g.nome); update the test in
tests/full-test.spec.js so the page.goto call uses the genre's name
(genres[0].nome) when building `${BASE}/catalogo?genere=...` (instead of
genres[0].id) so the request matches the FrontendController.php filtering logic.
In `@vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php`:
- Around line 306-310: The current LineFormatter (class LineFormatter) returns
the full stacktrace and normalizes directory separators by calling strtr on the
entire $trace which also converts namespace backslashes; update the logic in the
method that builds the stacktrace output (the block that returns
"\n{$this->indentStacktraces}[stacktrace]\n...") to avoid calling strtr on the
whole $trace — instead split the $trace into frames/lines, detect file path
segments in each frame (e.g., the "in /path/to/file.php:line" or quoted file
path portions), normalize DIRECTORY_SEPARATOR to '/' only for those file-path
substrings, and then rejoin the frames so class names and namespaces
(backslashes) remain intact while file paths are normalized. Ensure you touch
the code around the return that currently uses strtr($trace,
DIRECTORY_SEPARATOR, '/') and reference class LineFormatter and the $trace
variable when making the fix.
In `@vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php`:
- Around line 151-153: In collectLogs() ensure the opened file handle is closed
if flock($handle, LOCK_EX) fails: before the early return that currently happens
when false === flock($handle, LOCK_EX) call (in
DeduplicationHandler::collectLogs), call fclose($handle) (or otherwise release
the resource) so the file descriptor is not leaked; alternatively restructure
the locking path to use a try/finally or cleanup block that always calls
fclose($handle) after opening.
In `@vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php`:
- Around line 46-49: You modified a vendored class (ProcessHandler) by changing
documentation/formatting around the private float $timeout property; do not edit
vendor code directly—either revert this file to the Composer-managed version and
remove it from version control, submit your doc/formatting changes as a PR to
the Monolog upstream repository (referencing class ProcessHandler and property
$timeout), or apply the change via a Composer patch (e.g., using
cweagans/composer-patches) so it’s reproducible; restore the original vendor
file in the repo and implement whichever of the three workflows you choose.
In `@vendor/tecnickcom/tcpdf/config/tcpdf_config.php`:
- Line 11: Do not edit the vendor file in-tree; revert the corrupted change in
vendor/tecnickcom/tcpdf/config/tcpdf_config.php (the altered header line
containing "Copyright (C) 2004-2014 2026 Nicola Asuni") so the repository
matches the installed Composer package, then open an upstream issue/PR with the
TCPDF maintainers describing the malformed year and the related corruption seen
in PDF_HEADER_STRING so they can fix the package; reference tcpdf_config.php and
the PDF_HEADER_STRING constant in your report and attach the exact corrupted
line.
---
Outside diff comments:
In `@installer/classes/Installer.php`:
- Around line 1023-1036: The current parsing in Installer.php uses
str_contains($host, ':') and explode(':', $host, 2) which breaks bracketed IPv6
literals like [2001:db8::1]:8080; update the logic around $host/$port extraction
to first detect bracketed IPv6 (host starts with '[' and contains a ']') and if
so extract the text inside brackets as $hostOnly and anything after the closing
']' (after a colon) as $portPart, otherwise split on the last colon (use strrpos
semantics instead of explode) to avoid breaking IPv6 addresses without brackets;
then set $host and $port as before and keep the later base and defaultPorts
handling unchanged.
---
Duplicate comments:
In `@app/Views/libri/partials/book_form.php`:
- Around line 1378-1417: You are overriding a private Choices.js method
(authorsChoice._onEnterKey) which is fragile; instead attach a stable public
handler: listen to the actual input element (internalInput) keydown/keypress or
Choices public events (e.g. 'addItem' / 'choice' / 'search') and implement the
create-on-Enter logic there by detecting Enter, reading internalInput.value,
checking the dropdown highlighted/choices via public DOM queries (wrapper /
.choices__list--dropdown / .choices__item--selectable) and calling
createAuthorFromInputWithValue when appropriate; remove the override of
authorsChoice._onEnterKey and ensure the new handler prevents default and stops
propagation only when creating a new author.
In `@app/Views/partials/scroll-to-top.php`:
- Around line 5-6: The translated string is injected directly into JS strings
via btn.setAttribute('aria-label', '<?= __('Torna su') ?>') and
btn.setAttribute('title', '<?= __('Torna su') ?>'), which can break the script
if the translation contains quotes or script-close sequences; fix by
encoding/escaping the translation for JavaScript output (emit the translation as
a JSON-safe JS string using PHP's json_encode or an equivalent JS-escaping
helper) so the second argument to btn.setAttribute receives a properly escaped
string.
In `@tests/full-test.spec.js`:
- Around line 2269-2273: Guard the result of page.goto before calling
resp.status() to avoid errors when it returns null: check the returned value
from page.goto (resp) and treat a null response as a non-200 result (or extract
status via a safe check), then perform the assertion using that guarded status;
update the code around page.goto / resp.status() so it uses a conditional check
on resp (or a safe accessor) before calling status().
In `@vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php`:
- Around line 53-54: The constructor in RotatingFileHandler sets
$this->nextRotation before assigning $this->timezone which can cause rotation
boundaries to use the default timezone; move the assignment of $this->timezone
so it occurs before calling $this->getNextRotation() (i.e., ensure
$this->timezone is set prior to computing $this->nextRotation in the
constructor). Additionally, update the rotation-boundary calculations inside
getNextRotation() (and any DateTimeImmutable instantiations used for rotation
boundary or filename generation) to construct DateTimeImmutable objects with
$this->timezone (e.g., new DateTimeImmutable('now', $this->timezone)) so the
rotation boundary aligns with the configured filename timezone.
In `@vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php`:
- Around line 137-142: The inode-rotation logic in write() is reusing the
$retrying flag so an inode-triggered reopen can consume the one-time
write-retry; introduce a separate boolean (e.g. $inodeReopened or
$reopenedForInode) and use it for the hasUrlInodeWasChanged() / close() path
instead of mutating or relying on $retrying, or explicitly restore/reset
$retrying after the inode reopen, so the original write-failure retry behavior
remains intact; update references in write(), hasUrlInodeWasChanged() handling,
and any code paths that check $retrying to use the new flag where appropriate.
In `@vendor/tecnickcom/tcpdf/include/tcpdf_colors.php`:
- Line 10: Update the malformed copyright year range in the header string
"Copyright (C) 2002-2013 2026 Nicola Asuni - Tecnick.com LTD" to a single
continuous range (for example "Copyright (C) 2002-2026 Nicola Asuni -
Tecnick.com LTD") in tcpdf_colors.php so the year sequence is correct and no
duplicate years remain.
In `@vendor/tecnickcom/tcpdf/include/tcpdf_fonts.php`:
- Line 1387: The head table's checkSumAdjustment is being written at bytes
+4..+8 but per the TrueType/OpenType spec it must be at +8..+12; update the
write in the code that manipulates $font (the expression using
$table['head']['offset'], $offset and $checkSumAdjustment) so it inserts
pack('N', $checkSumAdjustment) starting at $table['head']['offset'] + $offset +
8 and continues to +12 (i.e., use substr boundaries +8 and +12 instead of +4 and
+8) to avoid corrupting subsetted TTF metadata.
In `@vendor/tecnickcom/tcpdf/include/tcpdf_images.php`:
- Line 277: Fix the typo "Unknownn" → "Unknown" in the PNG parser comments in
tcpdf_images.php: locate the PNG parsing section (the PNG parse function / PNG
parser comment blocks) and replace all occurrences of the misspelled "Unknownn"
(e.g., in comment strings like "Unknownn compression method") with "Unknown" so
the comments read correctly; ensure the three occurrences noted in the review
are corrected.
In `@vendor/tecnickcom/tcpdf/tcpdf.php`:
- Around line 23733-23737: The preg_split call that builds $rawparams is using a
capturing group '/([\,\s]+)/si' which causes delimiters to appear in the result;
update the preg_split invocation in the code that populates $rawparams to use a
non‑capturing group and the PREG_SPLIT_NO_EMPTY flag (e.g. '/(?:[,\s]+)/si' with
PREG_SPLIT_NO_EMPTY) so delimiters are not returned and you can remove the
subsequent array_filter step that tries to drop empty strings.
---
Nitpick comments:
In `@installer/database/data_de_DE.sql`:
- Around line 189-191: The seed contains duplicate upserts for the same setting
key 'cms.events_page_enabled' which causes the later INSERT to silently override
the earlier one; remove the redundant INSERT/ON DUPLICATE KEY UPDATE block so
there is a single canonical upsert for 'cms.events_page_enabled' (keep the
preferred values/description and delete the other occurrence), ensuring only one
source of truth for that setting in the SQL seed.
In `@vendor/tecnickcom/tcpdf/tcpdf.php`:
- Around line 4-6: This vendor file (tcpdf.php) has local modifications that
will be lost on Composer updates; stop editing vendor/tecnickcom/tcpdf/tcpdf.php
directly and instead either submit the fixes upstream to tecnickcom/TCPDF (open
a PR referencing the exact changes in tcpdf.php), maintain the fixes as a
project patch applied during install (e.g., add a patch entry using
cweagans/composer-patches referencing the modified tcpdf.php hunks), or fork the
TCPDF repo and require your fork in composer.json so the changes to tcpdf.php
persist across updates.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
composer.lockis excluded by!**/*.lock
📒 Files selected for processing (110)
.htaccessREADME.mdapp/Controllers/FrontendController.phpapp/Controllers/LibriController.phpapp/Controllers/UpdateController.phpapp/Models/BookRepository.phpapp/Routes/web.phpapp/Support/I18n.phpapp/Support/Updater.phpapp/Views/admin/updates.phpapp/Views/autori/index.phpapp/Views/editori/index.phpapp/Views/events/index.phpapp/Views/frontend/event-detail.phpapp/Views/frontend/events.phpapp/Views/frontend/home-sections/events.phpapp/Views/frontend/home-sections/genre_carousel.phpapp/Views/frontend/home.phpapp/Views/frontend/layout.phpapp/Views/layout.phpapp/Views/libri/index.phpapp/Views/libri/partials/book_form.phpapp/Views/libri/scheda_libro.phpapp/Views/partials/scroll-to-top.phpapp/Views/prestiti/index.phpapp/Views/utenti/index.phpfrontend/js/vendor.jsinstaller/classes/Installer.phpinstaller/database/data_de_DE.sqlinstaller/database/data_en_US.sqlinstaller/database/data_it_IT.sqlinstaller/index.phpinstaller/steps/step0.phplocale/de_DE.jsonlocale/routes_de_DE.jsonpublic/assets/main.csspublic/assets/vendor.bundle.jsscripts/.user.iniscripts/create-release.shscripts/manual-update.phpscripts/manual-upgrade.phptests/full-test.spec.jstests/genre-bugs.spec.jsvendor/composer/autoload_classmap.phpvendor/composer/autoload_files.phpvendor/composer/autoload_static.phpvendor/composer/installed.jsonvendor/composer/installed.phpvendor/graham-campbell/result-type/composer.jsonvendor/laravel/serializable-closure/composer.jsonvendor/laravel/serializable-closure/src/Serializers/Native.phpvendor/laravel/serializable-closure/src/Support/ReflectionClosure.phpvendor/monolog/monolog/README.mdvendor/monolog/monolog/composer.jsonvendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.phpvendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.phpvendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.phpvendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.phpvendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.phpvendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.phpvendor/monolog/monolog/src/Monolog/Handler/CubeHandler.phpvendor/monolog/monolog/src/Monolog/Handler/Curl/Util.phpvendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.phpvendor/monolog/monolog/src/Monolog/Handler/Handler.phpvendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.phpvendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.phpvendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.phpvendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.phpvendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.phpvendor/monolog/monolog/src/Monolog/Handler/StreamHandler.phpvendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.phpvendor/monolog/monolog/src/Monolog/Handler/TestHandler.phpvendor/slim/csrf/composer.jsonvendor/slim/psr7/composer.jsonvendor/slim/psr7/src/Cookies.phpvendor/slim/slim/MAINTAINERS.mdvendor/slim/slim/Slim/App.phpvendor/slim/slim/composer.jsonvendor/slim/slim/psalm.xmlvendor/tecnickcom/tcpdf/CHANGELOG.TXTvendor/tecnickcom/tcpdf/LICENSE.TXTvendor/tecnickcom/tcpdf/README.mdvendor/tecnickcom/tcpdf/VERSIONvendor/tecnickcom/tcpdf/composer.jsonvendor/tecnickcom/tcpdf/config/tcpdf_config.phpvendor/tecnickcom/tcpdf/fonts/ae_fonts_2.0/READMEvendor/tecnickcom/tcpdf/fonts/freefont-20100919/COPYINGvendor/tecnickcom/tcpdf/fonts/freefont-20100919/CREDITSvendor/tecnickcom/tcpdf/fonts/freefont-20120503/COPYINGvendor/tecnickcom/tcpdf/fonts/freefont-20120503/CREDITSvendor/tecnickcom/tcpdf/fonts/freefont-20120503/READMEvendor/tecnickcom/tcpdf/fonts/freefont-20120503/TROUBLESHOOTINGvendor/tecnickcom/tcpdf/include/barcodes/datamatrix.phpvendor/tecnickcom/tcpdf/include/barcodes/pdf417.phpvendor/tecnickcom/tcpdf/include/barcodes/qrcode.phpvendor/tecnickcom/tcpdf/include/tcpdf_colors.phpvendor/tecnickcom/tcpdf/include/tcpdf_filters.phpvendor/tecnickcom/tcpdf/include/tcpdf_font_data.phpvendor/tecnickcom/tcpdf/include/tcpdf_fonts.phpvendor/tecnickcom/tcpdf/include/tcpdf_images.phpvendor/tecnickcom/tcpdf/include/tcpdf_static.phpvendor/tecnickcom/tcpdf/tcpdf.phpvendor/tecnickcom/tcpdf/tcpdf_autoconfig.phpvendor/tecnickcom/tcpdf/tcpdf_barcodes_1d.phpvendor/tecnickcom/tcpdf/tcpdf_barcodes_2d.phpvendor/tecnickcom/tcpdf/tools/tcpdf_addfont.phpvendor/vlucas/phpdotenv/composer.jsonvendor/vlucas/phpdotenv/src/Parser/EntryParser.phpvendor/vlucas/phpdotenv/src/Repository/Adapter/EnvConstAdapter.phpvendor/vlucas/phpdotenv/src/Repository/Adapter/ServerConstAdapter.php
💤 Files with no reviewable changes (7)
- scripts/manual-update.php
- vendor/vlucas/phpdotenv/src/Repository/Adapter/ServerConstAdapter.php
- vendor/vlucas/phpdotenv/src/Repository/Adapter/EnvConstAdapter.php
- vendor/vlucas/phpdotenv/src/Parser/EntryParser.php
- vendor/slim/slim/psalm.xml
- vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php
- vendor/slim/slim/MAINTAINERS.md
| if (trim($trace) === '') { | ||
| return ''; | ||
| } | ||
|
|
||
| return "\n{$this->indentStacktraces}[stacktrace]\n{$this->indentStacktraces}" . strtr($trace, DIRECTORY_SEPARATOR, '/') . "\n"; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the exact code context in LineFormatter.php
cd vendor/monolog/monolog/src/Monolog/Formatter && head -320 LineFormatter.php | tail -30Repository: fabiodalez-dev/Pinakes
Length of output: 1051
🏁 Script executed:
# Let's get more context around the problematic code
cat -n vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php | sed -n '290,320p'Repository: fabiodalez-dev/Pinakes
Length of output: 1240
🏁 Script executed:
# Check what method this code is in and understand the full context
cat -n vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php | sed -n '280,320p'Repository: fabiodalez-dev/Pinakes
Length of output: 1571
🌐 Web query:
PHP Throwable getTraceAsString() format namespace separators backslash Windows
💡 Result:
Throwable::getTraceAsString() returns a plain PHP string containing the stack frames (e.g. #0 ... func()), with function/class names and file paths exactly as PHP records them. It does not have a special “Windows formatting” mode. [1]
So, on Windows you can naturally end up with lots of \ characters for two unrelated reasons:
- Namespaces in PHP use
\as the separator (e.g.Vendor\Package\Class::method). [2] - Windows file paths often contain
\(e.g.C:\path\to\file.php).
Why you might see double backslashes (\\)
If you pass the trace string through an escaping representation, backslashes will be shown doubled because backslash is the escape character in many output formats:
var_dump($trace)/var_export($trace, true)show an escaped representation of the string (so a single\is displayed as\\).json_encode($trace)must escape backslashes to produce valid JSON, so you’ll see\\in the JSON text. [3]
In all of those cases, the underlying string still contains single backslashes; you’re just viewing an escaped rendering.
What to do
- If you want “human readable” output:
echo $e->getTraceAsString();[1] - If you want full control over formatting (including normalizing slashes): use
getTrace()(array form) and format it yourself. [4]
Sources:
[1] PHP manual: Throwable::getTraceAsString() [1]
[2] PHP RFC discussing \ as the namespace separator [2]
[3] PHP manual: json_encode() (escaping in JSON strings) [3]
[4] PHP manual: Throwable::getTrace() [4]
🌐 Web query:
PHP strtr() DIRECTORY_SEPARATOR Windows namespace corruption stacktrace
💡 Result:
On Windows, DIRECTORY_SEPARATOR is \ (backslash) [1]. In PHP, backslash is also the namespace separator in fully-qualified class names like Foo\Bar [2][3].
So if you do something like:
$trace = strtr($trace, ['/' => DIRECTORY_SEPARATOR, '\\' => DIRECTORY_SEPARATOR]);(or any “normalize slashes” transform applied to the entire stacktrace string), you can accidentally rewrite namespaced identifiers inside the trace, making class names look like paths (or otherwise “corrupting” how the trace reads).
How to avoid namespace corruption
- Do not run slash normalization on the whole stacktrace text. Only normalize actual filesystem paths.
- Prefer forward slashes (
/) as your internal path separator (PHP on Windows accepts/in paths), and only deal with\when you receive Windows paths from APIs likerealpath()/__FILE__/__DIR__[2].
Practical pattern:
$bt = debug_backtrace();
foreach ($bt as &$f) {
if (isset($f['file'])) {
$f['file'] = str_replace('\\', '/', $f['file']); // normalize path only
}
}Sources: DIRECTORY_SEPARATOR meaning [1]; Windows accepts / and \ but \ is also namespaces [2]; PHP namespace separator is \ [3].
[1] https://www.php.net/manual/en/dir.constants.php
[2] https://stackoverflow.com/questions/32035756/what-is-the-difference-between-and-in-php
[3] https://wiki.php.net/rfc/backslashnamespaces
Line 310: Avoid normalizing the entire stacktrace string with strtr(DIRECTORY_SEPARATOR, '/').
On Windows, this replaces all backslashes including namespace separators in class names (e.g., Vendor\Class → Vendor/Class), corrupting stacktrace readability. Normalize only file-path portions per frame.
🔧 Suggested fix
- return "\n{$this->indentStacktraces}[stacktrace]\n{$this->indentStacktraces}" . strtr($trace, DIRECTORY_SEPARATOR, '/') . "\n";
+ $normalizedTrace = preg_replace_callback(
+ '/^(#\d+\s+)(.+?)(\(\d+\):)/m',
+ static fn (array $m): string => $m[1] . str_replace(DIRECTORY_SEPARATOR, '/', $m[2]) . $m[3],
+ $trace
+ ) ?? $trace;
+
+ return "\n{$this->indentStacktraces}[stacktrace]\n{$this->indentStacktraces}{$normalizedTrace}\n";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (trim($trace) === '') { | |
| return ''; | |
| } | |
| return "\n{$this->indentStacktraces}[stacktrace]\n{$this->indentStacktraces}" . strtr($trace, DIRECTORY_SEPARATOR, '/') . "\n"; | |
| if (trim($trace) === '') { | |
| return ''; | |
| } | |
| $normalizedTrace = preg_replace_callback( | |
| '/^(#\d+\s+)(.+?)(\(\d+\):)/m', | |
| static fn (array $m): string => $m[1] . str_replace(DIRECTORY_SEPARATOR, '/', $m[2]) . $m[3], | |
| $trace | |
| ) ?? $trace; | |
| return "\n{$this->indentStacktraces}[stacktrace]\n{$this->indentStacktraces}{$normalizedTrace}\n"; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php` around lines
306 - 310, The current LineFormatter (class LineFormatter) returns the full
stacktrace and normalizes directory separators by calling strtr on the entire
$trace which also converts namespace backslashes; update the logic in the method
that builds the stacktrace output (the block that returns
"\n{$this->indentStacktraces}[stacktrace]\n...") to avoid calling strtr on the
whole $trace — instead split the $trace into frames/lines, detect file path
segments in each frame (e.g., the "in /path/to/file.php:line" or quoted file
path portions), normalize DIRECTORY_SEPARATOR to '/' only for those file-path
substrings, and then rejoin the frames so class names and namespaces
(backslashes) remain intact while file paths are normalized. Ensure you touch
the code around the return that currently uses strtr($trace,
DIRECTORY_SEPARATOR, '/') and reference class LineFormatter and the $trace
variable when making the fix.
| if (false === flock($handle, LOCK_EX)) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's read the file with context around the flagged lines
head -200 vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php | tail -100Repository: fabiodalez-dev/Pinakes
Length of output: 3246
🏁 Script executed:
# Get a broader view: read the collectLogs method completely
# First, find the line range of the method
rg -n "function collectLogs|private function|protected function" vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.phpRepository: fabiodalez-dev/Pinakes
Length of output: 271
🏁 Script executed:
# Look at lines 130-170 to get full context of the flock logic
sed -n '130,170p' vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.phpRepository: fabiodalez-dev/Pinakes
Length of output: 1269
Close the file handle before returning on lock acquisition failure.
In collectLogs(), the file handle opened at line 148 is not closed when flock() fails at line 151–153. The early return bypasses the fclose($handle) at line 170, leaving the file descriptor open. Under lock contention, this accumulates resource leaks.
Proposed fix
if (false === flock($handle, LOCK_EX)) {
+ fclose($handle);
return;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (false === flock($handle, LOCK_EX)) { | |
| return; | |
| } | |
| if (false === flock($handle, LOCK_EX)) { | |
| fclose($handle); | |
| return; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php` around
lines 151 - 153, In collectLogs() ensure the opened file handle is closed if
flock($handle, LOCK_EX) fails: before the early return that currently happens
when false === flock($handle, LOCK_EX) call (in
DeduplicationHandler::collectLogs), call fclose($handle) (or otherwise release
the resource) so the file descriptor is not leaked; alternatively restructure
the locking path to use a try/finally or cleanup block that always calls
fclose($handle) after opening.
| private float $timeout; | ||
|
|
||
| /** | ||
| * @var array<int, string[]> | ||
| * @var array<int, list<string>> |
There was a problem hiding this comment.
Avoid modifying vendor files directly.
This file is part of the monolog/monolog package managed by Composer. Direct modifications to vendor/ files will be overwritten on the next composer install or composer update, causing these changes to be silently lost.
If these formatting/documentation improvements are desired:
- Contribute upstream: Submit a PR to the Monolog repository with these changes.
- Revert locally: Remove this file from version control and let Composer manage it.
If the vendor/ directory is intentionally committed, consider using Composer patches or excluding vendor from the repository entirely.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php` around lines
46 - 49, You modified a vendored class (ProcessHandler) by changing
documentation/formatting around the private float $timeout property; do not edit
vendor code directly—either revert this file to the Composer-managed version and
remove it from version control, submit your doc/formatting changes as a PR to
the Monolog upstream repository (referencing class ProcessHandler and property
$timeout), or apply the change via a Composer patch (e.g., using
cweagans/composer-patches) so it’s reproducible; restore the original vendor
file in the repo and implement whichever of the three workflows you choose.
| // License : GNU-LGPL v3 (https://www.gnu.org/copyleft/lesser.html) | ||
| // ------------------------------------------------------------------- | ||
| // Copyright (C) 2004-2014 Nicola Asuni - Tecnick.com LTD | ||
| // Copyright (C) 2004-2014 2026 Nicola Asuni - Tecnick.com LTD |
There was a problem hiding this comment.
Corrupted copyright year format in vendor file header.
Line 11 shows Copyright (C) 2004-2014 2026 Nicola Asuni which has malformed year formatting (likely should be 2004-2026 or 2004-2014, 2026). This appears to be part of the same corruption that affected PDF_HEADER_STRING.
Since this is a vendor file managed by Composer, these issues should be reported upstream to TCPDF maintainers rather than modified locally.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vendor/tecnickcom/tcpdf/config/tcpdf_config.php` at line 11, Do not edit the
vendor file in-tree; revert the corrupted change in
vendor/tecnickcom/tcpdf/config/tcpdf_config.php (the altered header line
containing "Copyright (C) 2004-2014 2026 Nicola Asuni") so the repository
matches the installed Composer package, then open an upstream issue/PR with the
TCPDF maintainers describing the malformed year and the related corruption seen
in PDF_HEADER_STRING so they can fix the package; reference tcpdf_config.php and
the PDF_HEADER_STRING constant in your report and attach the exact corrupted
line.
- Installer: handle bracketed IPv6 literals in host parsing - FrontendController: return 500 (not 404) for prepare() failures - LibriController: validate numero_pagine with filter_var, reject non-numeric - scroll-to-top: escape translated text with json_encode for JS safety - manual-upgrade: add brute-force protection (5 attempts / 5-min lockout) - book_form: replace private Choices.js _onEnterKey with public keydown handler - data_de_DE: remove duplicate events_page_enabled upsert - tests: use genre name (not ID) for catalog filter, guard resp?.status()
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
vendor/slim/psr7/composer.json (1)
1-73:⚠️ Potential issue | 🟠 MajorAvoid direct edits to
vendor/slim/psr7/composer.json.While
vendor/is tracked in this repository,slim/psr7is installed as a standard upstream dependency from GitHub. Direct edits circumvent Composer's dependency management and will conflict with futurecomposer install/updateoperations. If you need to modify this package, use a path repository in the rootcomposer.jsonor depend on a forked version instead.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/slim/psr7/composer.json` around lines 1 - 73, You edited the upstream dependency slim/psr7's composer.json directly in vendor, which bypasses Composer and will break future installs; revert that file and instead manage changes via Composer. Restore vendor/slim/psr7/composer.json to the original upstream state (undo changes to keys like "require", "provide", "autoload", etc.), then implement your intended modifications by adding a path repository or a forked package entry in the root composer.json and update composer.json to require your fork/path for the "slim/psr7" package so Composer manages the changed package correctly.app/Support/Updater.php (1)
994-1026:⚠️ Potential issue | 🟡 MinorTreat non-2xx
file_get_contentsdownload responses as failures.Because
ignore_errorsis enabled, Line 994 can return an error body for HTTP 4xx/5xx. In this fallback branch, non-auth HTTP errors are not currently forcing failure, so HTML/JSON error payloads may continue through ZIP validation instead of failing early with clearer handling.🔧 Proposed fix
$fileContent = `@file_get_contents`($downloadUrl, false, $context); // Log response headers (magic variable set by file_get_contents) /** `@var` array<int, string> $http_response_header */ @@ $dlStatus = 0; if (!empty($http_response_header[0]) && preg_match('/HTTP\/\d\.\d\s+(\d+)/', $http_response_header[0], $dlMatch)) { $dlStatus = (int) $dlMatch[1]; } + if ($dlStatus >= 400) { + $fileContent = false; + } if (in_array($dlStatus, [401, 403], true) && $this->githubToken !== '') { $savedToken = $this->githubToken; $this->githubToken = '';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Support/Updater.php` around lines 994 - 1026, The download logic in Updater.php currently leaves error bodies from file_get_contents (with ignore_errors=true) to proceed; after each file_get_contents call (the initial fetch that sets $fileContent and the retry branch that reassigns $fileContent after clearing $this->githubToken), inspect the HTTP status from $http_response_header (as parsed into $dlStatus) and treat any non-2xx status as a failure: set $fileContent to false or throw/return an error so ZIP validation is not attempted on an HTML/JSON error payload. Ensure this check uses the same parsing logic (preg_match on $http_response_header[0] to derive $dlStatus) and add a debugLog call (use $this->debugLog) with the headers and status when rejecting the response; preserve resetting $this->githubToken after the retry by keeping the existing try/finally.installer/classes/Installer.php (1)
1023-1036:⚠️ Potential issue | 🟡 MinorBracketed IPv6 host parsing can drop a valid forwarded/server port
At Line [1023], when host matches bracketed IPv6 without inline
:port,$portstaysnulland theelseifchain skipsHTTP_X_FORWARDED_PORT/SERVER_PORT. This can produce a wrongAPP_CANONICAL_URLon non-default ports.🛠️ Proposed fix
- if (preg_match('/^\[(.+)\](?::(\d+))?$/', $host, $matches)) { + if (preg_match('/^\[(.+)\](?::(\d+))?$/', $host, $matches)) { // Bracketed IPv6 literal, optionally with port $host = '[' . $matches[1] . ']'; $port = isset($matches[2]) ? (int) $matches[2] : null; } elseif (substr_count($host, ':') === 1) { // hostname:port or IPv4:port [$hostOnly, $portPart] = explode(':', $host, 2); $host = $hostOnly; $port = is_numeric($portPart) ? (int)$portPart : null; - } elseif (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) { - $port = (int)$_SERVER['HTTP_X_FORWARDED_PORT']; - } elseif (isset($_SERVER['SERVER_PORT']) && is_numeric((string)$_SERVER['SERVER_PORT'])) { - $port = (int)$_SERVER['SERVER_PORT']; } + if ($port === null && !empty($_SERVER['HTTP_X_FORWARDED_PORT'])) { + $port = (int)$_SERVER['HTTP_X_FORWARDED_PORT']; + } elseif ($port === null && isset($_SERVER['SERVER_PORT']) && is_numeric((string)$_SERVER['SERVER_PORT'])) { + $port = (int)$_SERVER['SERVER_PORT']; + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@installer/classes/Installer.php` around lines 1023 - 1036, The bracketed IPv6 branch in Installer:: (the preg_match('/^\[(.+)\](?::(\d+))?$/', $host, $matches)) handler sets $host and $port to null when no inline port is present, but uses elseif so the subsequent HTTP_X_FORWARDED_PORT / SERVER_PORT checks are skipped; adjust the logic so that after handling a bracketed IPv6 literal you still populate $port from $_SERVER['HTTP_X_FORWARDED_PORT'] or $_SERVER['SERVER_PORT'] when an inline port was not found (either by converting the elseif chain into separate ifs or by only setting $port when $matches[2] exists and leaving $port unset otherwise), ensuring $host remains '[...]' and $port is derived from forwarded/server values when needed.
♻️ Duplicate comments (13)
vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php (1)
48-54:⚠️ Potential issue | 🟠 MajorAlign rotation boundary with the configured timezone.
$this->nextRotationis computed before$this->timezoneis assigned, andgetNextRotation()still uses the server default timezone. Filename generation (Line 183) is timezone-aware, so rotation cutoff and filename date can diverge around midnight.Suggested fix
- $this->nextRotation = $this->getNextRotation(); $this->timezone = $timezone; + $this->nextRotation = $this->getNextRotation();protected function getNextRotation(): \DateTimeImmutable { + $now = new \DateTimeImmutable('now', $this->timezone); + return match (str_replace(['/','_','.'], '-', $this->dateFormat)) { - self::FILE_PER_MONTH => (new \DateTimeImmutable('first day of next month'))->setTime(0, 0, 0), - self::FILE_PER_YEAR => (new \DateTimeImmutable('first day of January next year'))->setTime(0, 0, 0), - default => (new \DateTimeImmutable('tomorrow'))->setTime(0, 0, 0), + self::FILE_PER_MONTH => $now->modify('first day of next month')->setTime(0, 0, 0), + self::FILE_PER_YEAR => $now->modify('first day of January next year')->setTime(0, 0, 0), + default => $now->modify('tomorrow')->setTime(0, 0, 0), }; }Also applies to: 226-232
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php` around lines 48 - 54, The constructor sets $this->nextRotation by calling getNextRotation() before assigning $this->timezone, so getNextRotation() uses the server default timezone and can disagree with timezone-aware filename generation; move the assignment of $this->timezone (and any timezone-dependent setup) before calling $this->getNextRotation() (affecting the __construct in RotatingFileHandler and the same pattern around lines 226-232), or modify getNextRotation() to accept a timezone parameter and pass $this->timezone to it after setting it; ensure calls to setFilenameFormat(...) remain unchanged so filenames and rotation cutoff use the same timezone.vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php (1)
137-142:⚠️ Potential issue | 🟠 MajorRetry state is still coupled between inode-rotation and write-failure paths.
$this->retryingis set for inode-change handling (Line 276), then the recursive call at Line 139 can no longer take the write-failure retry branch at Line 187. This suppresses the intended one-time retry after reopen.Suggested fix
protected function write(LogRecord $record): void { if ($this->hasUrlInodeWasChanged()) { + $this->inodeUrl = null; $this->close(); $this->write($record); return; } @@ private function hasUrlInodeWasChanged(): bool { - if ($this->inodeUrl === null || $this->retrying || $this->inodeUrl === $this->getInodeFromUrl()) { + if ($this->inodeUrl === null || $this->inodeUrl === $this->getInodeFromUrl()) { return false; } - - $this->retrying = true; return true; }Also applies to: 270-277
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php` around lines 137 - 142, The inode-rotation path sets the instance-level flag $this->retrying (in methods like hasUrlInodeWasChanged()/the inode-change block) and then calls $this->write($record) recursively, which prevents the write-failure retry branch from executing; update the inode-rotation flow (the block that calls close() then write($record)) to clear or localize the retry state before the recursive call (e.g., save $prevRetry = $this->retrying; $this->retrying = false; $this->close(); $this->write($record); $this->retrying = $prevRetry) so that write() can independently perform its one-time retry on failure; apply the same change where retrying is set in the inode-change handling to avoid coupling the two paths.vendor/laravel/serializable-closure/src/Support/ReflectionClosure.php (1)
1381-1387:⚠️ Potential issue | 🟠 Major
verifyCandidateSignaturemisparses parameter lists with nested parentheses.The state machine exits
paramson the first)token, so defaults likefn($a = foo(), $b) => ...can be parsed incorrectly and rejected.💡 Proposed fix
$params = []; $vars = []; $state = 'start'; + $depth = 0; foreach ($tokens as $token) { if (! is_array($token)) { if ($token === '(' && $state === 'start') { $state = 'params'; - } elseif ($token === ')' && $state === 'params') { - $state = 'body'; + $depth = 1; + } elseif ($token === '(' && $state === 'params') { + $depth++; + } elseif ($token === ')' && $state === 'params') { + $depth--; + if ($depth === 0) { + $state = 'body'; + } } continue; }Use this read-only verification to confirm the missing depth tracking:
#!/bin/bash set -euo pipefail FILE="vendor/laravel/serializable-closure/src/Support/ReflectionClosure.php" echo "=== verifyCandidateSignature block ===" sed -n '1363,1405p' "$FILE" echo echo "=== Look for params->body transition without depth handling ===" rg -n "token === '\\)' && \\$state === 'params'" "$FILE" rg -n "\\$depth" "$FILE" || trueExpected result: the transition exists, and no local
$depthhandling is present in this method.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/laravel/serializable-closure/src/Support/ReflectionClosure.php` around lines 1381 - 1387, The state machine in verifyCandidateSignature iterates tokens and switches from 'params' to 'body' on any ')' token, which misparses nested parentheses in parameter defaults (e.g., fn($a = foo(), $b)); update the logic in ReflectionClosure::verifyCandidateSignature to track a depth counter while in the 'params' state (increment on '(' and decrement on ')') and only transition to 'body' when you see a ')' and depth is zero, ensuring nested parentheses do not prematurely end parameter parsing; adjust any variables/conditions around the foreach over $tokens to use this $depth check and initialize/reset $depth appropriately.vendor/tecnickcom/tcpdf/include/barcodes/pdf417.php (1)
10-10:⚠️ Potential issue | 🟡 MinorCorrect malformed copyright year range.
Line 10 currently has an invalid format (
2010-2013 2026).Suggested fix
-// Copyright (C) 2010-2013 2026 Nicola Asuni - Tecnick.com LTD +// Copyright (C) 2010-2026 Nicola Asuni - Tecnick.com LTD🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/include/barcodes/pdf417.php` at line 10, Update the malformed copyright header string "2010-2013 2026" in the file's top copyright comment to a valid format (for example "2010-2013, 2026") so the year range is correctly punctuated; locate the copyright comment containing "2010-2013 2026" and replace it with the corrected text.vendor/tecnickcom/tcpdf/include/barcodes/datamatrix.php (1)
10-10:⚠️ Potential issue | 🟡 MinorFix malformed copyright year range.
Line 10 has an invalid year format (
2010-2014 2026).Suggested fix
-// Copyright (C) 2010-2014 2026 Nicola Asuni - Tecnick.com LTD +// Copyright (C) 2010-2026 Nicola Asuni - Tecnick.com LTD🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/include/barcodes/datamatrix.php` at line 10, The copyright header contains a malformed year token "2010-2014 2026"; locate that exact string in vendor/tecnickcom/tcpdf/include/barcodes/datamatrix.php and replace it with a valid format such as "2010-2014, 2026" (or a single continuous range like "2010-2026" if that's intended) so the year range/token is syntactically correct.vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php (1)
151-153:⚠️ Potential issue | 🟠 MajorClose the file handle before returning on lock failure.
At Line 151–Line 153, the early return bypasses
fclose($handle), causing a resource leak whenflockfails.🔧 Suggested fix
if (false === flock($handle, LOCK_EX)) { + fclose($handle); return; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php` around lines 151 - 153, The early return after flock($handle, LOCK_EX) failing leaks the open resource; update the code in DeduplicationHandler (the block using $handle and flock) to call fclose($handle) before returning on lock failure (i.e., replace "if (false === flock($handle, LOCK_EX)) { return; }" with a branch that calls fclose($handle) then returns) so the file handle is always closed when the function exits early.vendor/tecnickcom/tcpdf/include/tcpdf_images.php (1)
277-277:⚠️ Potential issue | 🟡 MinorFix repeated typo in PNG diagnostics (
Unknownn→Unknown).At Line 277, Line 282, and Line 334, diagnostic comments contain a misspelling that should be corrected for clarity and consistency.
Also applies to: 282-282, 334-334
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/include/tcpdf_images.php` at line 277, Replace the repeated typo in the PNG diagnostic comments where the message reads "Unknownn compression method" to the correct spelling "Unknown compression method"; locate the occurrences of that string in tcpdf_images.php (the PNG diagnostics branch handled in the image parsing logic) and update the comment/text at each occurrence (previously at the lines containing "Unknownn compression method") to the corrected form to ensure consistency.vendor/tecnickcom/tcpdf/config/tcpdf_config.php (1)
11-11:⚠️ Potential issue | 🟡 MinorRevert malformed TCPDF header text values.
Line 11 has a corrupted copyright year format, and Line 117 has malformed
PDF_HEADER_STRINGtext (by2026).🔧 Suggested fix
-// Copyright (C) 2004-2014 2026 Nicola Asuni - Tecnick.com LTD +// Copyright (C) 2004-2026 Nicola Asuni - Tecnick.com LTD ... -define ('PDF_HEADER_STRING', "by2026 Nicola Asuni - Tecnick.com\nwww.tcpdf.org"); +define ('PDF_HEADER_STRING', "by Nicola Asuni - Tecnick.com\nwww.tcpdf.org");Also applies to: 117-117
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/config/tcpdf_config.php` at line 11, The file has corrupted header text and a malformed PDF header string; restore the original header comment to "Copyright (C) 2004-2014 Nicola Asuni - Tecnick.com LTD" (fix the corrupted year/text around the author) and update the PDF_HEADER_STRING constant (symbol PDF_HEADER_STRING) to remove the stray "2026" so it reads the intended phrase (e.g., "by Tecnick.com LTD" or simply "by"). Locate the header comment and the PDF_HEADER_STRING definition in tcpdf_config.php and replace the malformed values with the correct original texts.vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php (1)
310-310:⚠️ Potential issue | 🟠 MajorAvoid normalizing the entire stacktrace string.
At Line 310,
strtr($trace, DIRECTORY_SEPARATOR, '/')rewrites non-path backslashes too (notably PHP namespaces on Windows), which degrades stacktrace readability.🔧 Suggested fix
- return "\n{$this->indentStacktraces}[stacktrace]\n{$this->indentStacktraces}" . strtr($trace, DIRECTORY_SEPARATOR, '/') . "\n"; + return "\n{$this->indentStacktraces}[stacktrace]\n{$this->indentStacktraces}{$trace}\n";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php` at line 310, The code in Monolog\Formatter\LineFormatter currently normalizes all backslashes in the $trace string (strtr($trace, DIRECTORY_SEPARATOR, '/')), which also converts PHP namespace separators and harms readability; update the logic in Monolog\Formatter\LineFormatter (the stacktrace formatting code that builds $trace) to stop running a global strtr on the whole stacktrace and instead normalize directory separators only inside actual file path substrings—e.g., use a preg_replace_callback to find file path patterns (Windows drive paths or UNC paths and the file:line fragments produced by backtraces) and replace backslashes with '/' only within those matches, leaving namespace backslashes untouched.vendor/tecnickcom/tcpdf/include/tcpdf_fonts.php (1)
1387-1387:⚠️ Potential issue | 🔴 CriticalFix
checkSumAdjustmentwrite offset (reintroduced regression).Line 1387 writes the checksum at
+4..+8, which targets the wrongheadfield. This should be written at+8..+12.🔧 Proposed fix
-$font = substr($font, 0, $table['head']['offset'] + $offset + 4).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + $offset + 8); +$font = substr($font, 0, $table['head']['offset'] + $offset + 8).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + $offset + 12);#!/bin/bash # Verify current checkSumAdjustment write location in subset rebuild logic. rg -n "set checkSumAdjustment on head table|pack\\('N', \\$checkSumAdjustment\\)" vendor/tecnickcom/tcpdf/include/tcpdf_fonts.php -C3 sed -n '1383,1389p' vendor/tecnickcom/tcpdf/include/tcpdf_fonts.php🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/include/tcpdf_fonts.php` at line 1387, The checkSumAdjustment is currently written at the wrong offset in the head table: update the write location so the packed 4-byte checkSumAdjustment is inserted at +8..+12 instead of +4..+8; specifically modify the expression that builds $font (the line using substr($font, 0, $table['head']['offset'] + $offset + 4).pack('N', $checkSumAdjustment).substr(...)) to use +8 (i.e. $table['head']['offset'] + $offset + 8) for the start of the pack insertion so the packed value lands at bytes +8..+12 relative to the head table.vendor/tecnickcom/tcpdf/tcpdf.php (3)
2909-2914:⚠️ Potential issue | 🟠 Major
setCompression()is bypassed by an unconditional early return.Line 2910 returns immediately, so
$compressandgzcompresschecks are never used, and the rest of the method is dead code.🔧 Proposed fix
public function setCompression($compress=true) { - $this->compress = false; return; - if (function_exists('gzcompress')) { - if ($compress) { - $this->compress = true; - } - } + $this->compress = false; + if (function_exists('gzcompress') && $compress) { + if (!$this->pdfa_mode) { + $this->compress = true; + } + } }#!/bin/bash set -euo pipefail FILE="vendor/tecnickcom/tcpdf/tcpdf.php" echo "[local]" rg -n -C3 'public function setCompression|\$this->compress = false; return;|\$this->compress = true;|pdfa_mode' "$FILE" echo echo "[upstream 6.11.0]" curl -fsSL https://raw.githubusercontent.com/tecnickcom/TCPDF/6.11.0/tcpdf.php \ | rg -n -C3 'public function setCompression|\$this->compress = false; return;|\$this->compress = true;|pdfa_mode'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/tcpdf.php` around lines 2909 - 2914, The setCompression method currently unconditionally sets $this->compress = false and returns, making the rest of the function dead; remove the early unconditional assignment and return so the method honors the $compress parameter and the gzcompress availability check, i.e., in function setCompression(...) use function_exists('gzcompress') and then set $this->compress based on the incoming $compress value (and pdfa_mode if needed) rather than always forcing false and returning early.
23733-23737:⚠️ Potential issue | 🟠 MajorSVG path tokenization keeps delimiters and can skew parameter indexing.
Line 23734 uses a capturing group in
preg_split, so delimiters may be retained in$rawparamsbefore numeric conversion.🔧 Proposed fix
- $rawparams = preg_split('/([\,\s]+)/si', trim($val[2])); - $rawparams = array_filter($rawparams, function($p) { - return trim($p) != ''; - }); + $rawparams = preg_split('/[\,\s]+/si', trim($val[2]), -1, PREG_SPLIT_NO_EMPTY);#!/bin/bash set -euo pipefail FILE="vendor/tecnickcom/tcpdf/tcpdf.php" echo "[local svg split code]" rg -n -C2 "preg_split\\('/\\(\\[\\\\,\\\\s\\]\\+\\)/si'|PREG_SPLIT_NO_EMPTY|array_filter\\(\\$rawparams" "$FILE" echo echo "[upstream 6.11.0 svg split code]" curl -fsSL https://raw.githubusercontent.com/tecnickcom/TCPDF/6.11.0/tcpdf.php \ | rg -n -C2 "preg_split\\('/\\(\\[\\\\,\\\\s\\]\\+\\)/si'|PREG_SPLIT_NO_EMPTY|array_filter\\(\\$rawparams"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/tcpdf.php` around lines 23733 - 23737, The preg_split call that populates $rawparams currently uses a capturing group, causing delimiters to be kept and skew parameter positions; change the split to avoid capturing (use a non-capturing group) or pass the PREG_SPLIT_NO_EMPTY flag so delimiters are not returned, then remove the array_filter cleanup; update the preg_split invocation that fills $rawparams (and subsequent handling of $rawparams) to rely on the cleaned tokens only so numeric parameter indexing is correct.
6925-6927:⚠️ Potential issue | 🟠 Major
fitBlock()bottom clamp can exceed the page-break boundary.Line 6925-Line 6926 clamps against
PageBreakTrigger + bMargin, which can place content into the footer margin whenfitonpageis true.🔧 Proposed fix
- if (($y + $h) > $this->PageBreakTrigger + $this->bMargin) { - $h = $this->PageBreakTrigger + $this->bMargin - $y; + if (($y + $h) > $this->PageBreakTrigger) { + $h = $this->PageBreakTrigger - $y; $w = ($h * $ratio_wh); }#!/bin/bash set -euo pipefail FILE="vendor/tecnickcom/tcpdf/tcpdf.php" echo "[local fitBlock clamp]" rg -n -C3 'protected function fitBlock|PageBreakTrigger \+ \$this->bMargin|PageBreakTrigger - \$y' "$FILE" echo echo "[upstream 6.11.0 fitBlock clamp]" curl -fsSL https://raw.githubusercontent.com/tecnickcom/TCPDF/6.11.0/tcpdf.php \ | rg -n -C3 'protected function fitBlock|\(\$y \+ \$h\) > \$this->PageBreakTrigger(\s*\+\s*\$this->bMargin)?'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/tcpdf.php` around lines 6925 - 6927, The fitBlock() bottom-clamp currently uses $this->PageBreakTrigger + $this->bMargin which can push content into the footer margin when fitonpage is true; change the clamp so $h is calculated as $this->PageBreakTrigger - $y (i.e. $h = $this->PageBreakTrigger - $y) and then recompute $w using the existing $ratio_wh logic (variables: fitBlock, PageBreakTrigger, bMargin, fitonpage, $y, $h, $w, $ratio_wh).
🧹 Nitpick comments (6)
app/Models/BookRepository.php (1)
345-366: Extract shared metadata normalization to reduce create/update drift.These new blocks are mirrored in both methods; extracting one shared normalizer/mapping would make future field changes safer.
♻️ Refactor sketch
- if ($this->hasColumn('lingua')) { - $addField('lingua', 's', $data['lingua'] ?? null); - } - if ($this->hasColumn('anno_pubblicazione')) { - $annoRaw = $data['anno_pubblicazione'] ?? null; - $anno = filter_var($annoRaw, FILTER_VALIDATE_INT); - $addField('anno_pubblicazione', 'i', $anno === false ? null : $anno); - } - if ($this->hasColumn('edizione')) { - $addField('edizione', 's', $data['edizione'] ?? null); - } - if ($this->hasColumn('traduttore')) { - $addField('traduttore', 's', $data['traduttore'] ?? null); - } - if ($this->hasColumn('illustratore')) { - $addField('illustratore', 's', $data['illustratore'] ?? null); - } - if ($this->hasColumn('numero_pagine')) { - $numPagineRaw = $data['numero_pagine'] ?? null; - $numPagine = filter_var($numPagineRaw, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]); - $addField('numero_pagine', 'i', $numPagine === false ? null : $numPagine); - } + foreach ($this->normalizedBookMeta($data) as [$column, $type, $value]) { + if ($this->hasColumn($column)) { + $addField($column, $type, $value); + } + }+ /** + * `@return` array<int, array{0:string,1:string,2:mixed}> + */ + private function normalizedBookMeta(array $data): array + { + $annoRaw = $data['anno_pubblicazione'] ?? null; + $anno = filter_var($annoRaw, FILTER_VALIDATE_INT); + $numPagineRaw = $data['numero_pagine'] ?? null; + $numPagine = filter_var($numPagineRaw, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]); + + return [ + ['lingua', 's', $data['lingua'] ?? null], + ['anno_pubblicazione', 'i', $anno === false ? null : $anno], + ['edizione', 's', $data['edizione'] ?? null], + ['traduttore', 's', $data['traduttore'] ?? null], + ['illustratore', 's', $data['illustratore'] ?? null], + ['numero_pagine', 'i', $numPagine === false ? null : $numPagine], + ]; + }Also applies to: 671-692
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Models/BookRepository.php` around lines 345 - 366, The repeated field-normalization block (using hasColumn, addField, and validations for anno_pubblicazione and numero_pagine) should be extracted into a single private helper (e.g., normalizeBookMetadata or prepareBookFields) that accepts the incoming $data and either returns a normalized map of field => [type, value] or accepts the $addField callable to invoke directly; move the filter_var logic for anno_pubblicazione and numero_pagine into that helper (preserve min_range validation), include the string defaults for lingua/edizione/traduttore/illustratore, and then call this helper from both create and update to iterate and apply addField so future field changes stay in one place.tests/full-test.spec.js (1)
49-49: Clarify the SQL-safety claim in the helper comment.Line 49 says “injection-safe”, but the file still builds many SQL statements via interpolation. This helper is shell-safe, not SQL-injection-safe.
✏️ Suggested wording
-/** Execute a MySQL query and return trimmed output (injection-safe, no shell). */ +/** Execute a MySQL query and return trimmed output (shell-safe; caller must escape SQL values). */🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/full-test.spec.js` at line 49, Update the helper's comment to remove the misleading "injection-safe" claim and clarify that it is safe from shell injection but not safe from SQL injection; replace the phrase in the JSDoc on the helper in tests/full-test.spec.js (the comment that begins "/** Execute a MySQL query and return trimmed output (injection-safe, no shell). */") with wording such as "shell-safe (does not use the shell), but not SQL-injection-safe — callers must use parameterized queries or proper escaping when constructing SQL."vendor/slim/csrf/composer.json (1)
39-50: Move CI/test scripts from vendored package to root composer.json.Lines 39-50 add sniffer and phpunit scripts directly to
vendor/slim/csrf/composer.json. These edits will be lost when the slim/csrf dependency is updated or reinstalled. Move these scripts to the repository rootcomposer.jsoninstead.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/slim/csrf/composer.json` around lines 39 - 50, The composer.json in the vendored package incorrectly adds project CI/test scripts ("sniffer:check", "sniffer:fix", "test", "check", "test:coverage"); remove these script entries from vendor/slim/csrf/composer.json and instead add the same script definitions to your repository root composer.json "scripts" section so they persist across dependency updates; update any script references (e.g., "@sniffer:check", "@test:coverage") in the root file as needed and run composer dump-autoload / composer update locally to ensure the root scripts are registered.app/Support/Updater.php (1)
748-751: Centralize retry headers to avoid drift across retry paths.The unauthenticated retry header arrays are duplicated in multiple places. Consider deriving them via a single helper (e.g.,
getGitHubHeaders(...)with an explicitwithAuthflag) to keepUser-Agent/Acceptbehavior consistent over time.Also applies to: 948-951
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Support/Updater.php` around lines 748 - 751, Duplicate unauthenticated retry header arrays ($retryHeaders) are causing drift; create a single helper (e.g., getGitHubHeaders(bool $withAuth = false, ?string $token = null)) in Updater.php that returns the canonical array including 'User-Agent' and 'Accept', and conditionally adds an Authorization header when $withAuth is true, then replace the inline $retryHeaders definitions (the occurrences around the current block and the ones at the later occurrence) with calls to getGitHubHeaders(...) so all retry paths use the same header set.app/Controllers/FrontendController.php (1)
1299-1322: Consider replacing per-level descendant queries with a single tree load + shared traversal helperCurrent BFS performs one SQL query per depth level and duplicates traversal logic already present in
collectGenreTreeIds(). A singleSELECT id,parent_id FROM generi+ in-memory traversal would reduce DB round-trips and keep one traversal implementation.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Controllers/FrontendController.php` around lines 1299 - 1322, The per-level BFS SQL loop using repeated prepares (variables $queue, $visited and the descendant query) should be replaced by a single SELECT id,parent_id FROM generi to load the tree into memory and then perform the traversal in PHP (reusing or delegating to the existing collectGenreTreeIds() helper) to collect the requested genre plus all descendants; remove the repeated prepare/execute block and instead build an adjacency map from the single result set and run a local BFS/DFS to populate $visited (or return the id list) so you eliminate multiple DB round-trips and centralize traversal logic.app/Routes/web.php (1)
257-282: Hybrid profile-update routes are now harder to scale with dynamic locales.The
if/elseifper-locale block will require code changes for each new language. Consider centralizing hybrid aliases in a small map to keep this maintainable.♻️ Suggested refactor
- if ($locale === 'it_IT') { - // Italian base + English action - $registerRouteIfUnique('POST', '/profilo/update', function (...) { ... }, [...]); - } elseif ($locale === 'en_US') { - // English base + Italian action - $registerRouteIfUnique('POST', '/profile/aggiorna', function (...) { ... }, [...]); - } elseif ($locale === 'de_DE') { - // German base + English/Italian action - $registerRouteIfUnique('POST', '/profil/update', function (...) { ... }, [...]); - $registerRouteIfUnique('POST', '/profil/aggiorna', function (...) { ... }, [...]); - } + $profileHybridRoutes = [ + 'it_IT' => ['/profilo/update'], + 'en_US' => ['/profile/aggiorna'], + 'de_DE' => ['/profil/update', '/profil/aggiorna'], + ]; + foreach ($profileHybridRoutes[$locale] ?? [] as $hybridRoute) { + $registerRouteIfUnique('POST', $hybridRoute, function ($request, $response) use ($app) { + $db = $app->getContainer()->get('db'); + $controller = new ProfileController(); + return $controller->update($request, $response, $db); + }, [new CsrfMiddleware(), new AuthMiddleware(['admin', 'staff', 'standard', 'premium'])]); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Routes/web.php` around lines 257 - 282, The current per-locale if/elseif block around $locale with multiple calls to $registerRouteIfUnique for ProfileController->update duplicates middleware and is unscalable; replace it by defining a centralized map of locale => array of route aliases (e.g., $routeAliasesByLocale) and iterate over that map to call $registerRouteIfUnique for each alias, reusing the same middleware array [new CsrfMiddleware(), new AuthMiddleware(['admin','staff','standard','premium'])] and the same controller instantiation (ProfileController) and $db retrieval, so adding a new language only requires updating the map instead of editing the if/elseif logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/Controllers/LibriController.php`:
- Around line 640-642: The BookRepository::updateOptionals method currently
accepts raw $data which allows invalid numero_pagine through; either
validate/sanitize numero_pagine inside updateOptionals (apply the same
filter_var(..., FILTER_VALIDATE_INT, ['options'=>['min_range'=>1]]) and set null
on invalid before casting) or change LibriController to pass the
already-validated $fields array to updateOptionals instead of raw request data;
updateOptionals should enforce the min_range constraint on numero_pagine (and
mirror the controller’s validation logic) so callers cannot bypass it.
In `@app/Views/partials/scroll-to-top.php`:
- Around line 25-37: The scroll visibility logic currently only runs on the
scroll event so when the page loads already scrolled past 400px the button (btn)
remains hidden; extract or replicate the same logic that computes var show =
window.scrollY > 400 and updates visible, btn.style.opacity,
btn.style.pointerEvents, btn.setAttribute('aria-hidden', ...) and btn.tabIndex,
and invoke that update once immediately after setup (either by extracting it
into an updateVisibility function and calling it, or by duplicating the logic
right after window.addEventListener) so the initial state reflects the current
scroll position.
In `@scripts/manual-upgrade.php`:
- Around line 291-292: The POST upgrade handler begins privileged state changes
when $authenticated && $requestMethod === 'POST' && isset($_FILES['zipfile'])
without CSRF protection; add CSRF validation by requiring a server-stored token
(e.g., in $_SESSION['csrf_token']) and verifying it against a POSTed token
(e.g., $_POST['csrf_token']) before proceeding with the upgrade logic in that
block, and on mismatch reject the request, log the event (append to $log), and
do not process the uploaded file; ensure the token is generated and embedded in
the upgrade form output so legitimate requests include it.
---
Outside diff comments:
In `@app/Support/Updater.php`:
- Around line 994-1026: The download logic in Updater.php currently leaves error
bodies from file_get_contents (with ignore_errors=true) to proceed; after each
file_get_contents call (the initial fetch that sets $fileContent and the retry
branch that reassigns $fileContent after clearing $this->githubToken), inspect
the HTTP status from $http_response_header (as parsed into $dlStatus) and treat
any non-2xx status as a failure: set $fileContent to false or throw/return an
error so ZIP validation is not attempted on an HTML/JSON error payload. Ensure
this check uses the same parsing logic (preg_match on $http_response_header[0]
to derive $dlStatus) and add a debugLog call (use $this->debugLog) with the
headers and status when rejecting the response; preserve resetting
$this->githubToken after the retry by keeping the existing try/finally.
In `@installer/classes/Installer.php`:
- Around line 1023-1036: The bracketed IPv6 branch in Installer:: (the
preg_match('/^\[(.+)\](?::(\d+))?$/', $host, $matches)) handler sets $host and
$port to null when no inline port is present, but uses elseif so the subsequent
HTTP_X_FORWARDED_PORT / SERVER_PORT checks are skipped; adjust the logic so that
after handling a bracketed IPv6 literal you still populate $port from
$_SERVER['HTTP_X_FORWARDED_PORT'] or $_SERVER['SERVER_PORT'] when an inline port
was not found (either by converting the elseif chain into separate ifs or by
only setting $port when $matches[2] exists and leaving $port unset otherwise),
ensuring $host remains '[...]' and $port is derived from forwarded/server values
when needed.
In `@vendor/slim/psr7/composer.json`:
- Around line 1-73: You edited the upstream dependency slim/psr7's composer.json
directly in vendor, which bypasses Composer and will break future installs;
revert that file and instead manage changes via Composer. Restore
vendor/slim/psr7/composer.json to the original upstream state (undo changes to
keys like "require", "provide", "autoload", etc.), then implement your intended
modifications by adding a path repository or a forked package entry in the root
composer.json and update composer.json to require your fork/path for the
"slim/psr7" package so Composer manages the changed package correctly.
---
Duplicate comments:
In `@vendor/laravel/serializable-closure/src/Support/ReflectionClosure.php`:
- Around line 1381-1387: The state machine in verifyCandidateSignature iterates
tokens and switches from 'params' to 'body' on any ')' token, which misparses
nested parentheses in parameter defaults (e.g., fn($a = foo(), $b)); update the
logic in ReflectionClosure::verifyCandidateSignature to track a depth counter
while in the 'params' state (increment on '(' and decrement on ')') and only
transition to 'body' when you see a ')' and depth is zero, ensuring nested
parentheses do not prematurely end parameter parsing; adjust any
variables/conditions around the foreach over $tokens to use this $depth check
and initialize/reset $depth appropriately.
In `@vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php`:
- Line 310: The code in Monolog\Formatter\LineFormatter currently normalizes all
backslashes in the $trace string (strtr($trace, DIRECTORY_SEPARATOR, '/')),
which also converts PHP namespace separators and harms readability; update the
logic in Monolog\Formatter\LineFormatter (the stacktrace formatting code that
builds $trace) to stop running a global strtr on the whole stacktrace and
instead normalize directory separators only inside actual file path
substrings—e.g., use a preg_replace_callback to find file path patterns (Windows
drive paths or UNC paths and the file:line fragments produced by backtraces) and
replace backslashes with '/' only within those matches, leaving namespace
backslashes untouched.
In `@vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php`:
- Around line 151-153: The early return after flock($handle, LOCK_EX) failing
leaks the open resource; update the code in DeduplicationHandler (the block
using $handle and flock) to call fclose($handle) before returning on lock
failure (i.e., replace "if (false === flock($handle, LOCK_EX)) { return; }" with
a branch that calls fclose($handle) then returns) so the file handle is always
closed when the function exits early.
In `@vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php`:
- Around line 48-54: The constructor sets $this->nextRotation by calling
getNextRotation() before assigning $this->timezone, so getNextRotation() uses
the server default timezone and can disagree with timezone-aware filename
generation; move the assignment of $this->timezone (and any timezone-dependent
setup) before calling $this->getNextRotation() (affecting the __construct in
RotatingFileHandler and the same pattern around lines 226-232), or modify
getNextRotation() to accept a timezone parameter and pass $this->timezone to it
after setting it; ensure calls to setFilenameFormat(...) remain unchanged so
filenames and rotation cutoff use the same timezone.
In `@vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php`:
- Around line 137-142: The inode-rotation path sets the instance-level flag
$this->retrying (in methods like hasUrlInodeWasChanged()/the inode-change block)
and then calls $this->write($record) recursively, which prevents the
write-failure retry branch from executing; update the inode-rotation flow (the
block that calls close() then write($record)) to clear or localize the retry
state before the recursive call (e.g., save $prevRetry = $this->retrying;
$this->retrying = false; $this->close(); $this->write($record); $this->retrying
= $prevRetry) so that write() can independently perform its one-time retry on
failure; apply the same change where retrying is set in the inode-change
handling to avoid coupling the two paths.
In `@vendor/tecnickcom/tcpdf/config/tcpdf_config.php`:
- Line 11: The file has corrupted header text and a malformed PDF header string;
restore the original header comment to "Copyright (C) 2004-2014 Nicola Asuni -
Tecnick.com LTD" (fix the corrupted year/text around the author) and update the
PDF_HEADER_STRING constant (symbol PDF_HEADER_STRING) to remove the stray "2026"
so it reads the intended phrase (e.g., "by Tecnick.com LTD" or simply "by").
Locate the header comment and the PDF_HEADER_STRING definition in
tcpdf_config.php and replace the malformed values with the correct original
texts.
In `@vendor/tecnickcom/tcpdf/include/barcodes/datamatrix.php`:
- Line 10: The copyright header contains a malformed year token "2010-2014
2026"; locate that exact string in
vendor/tecnickcom/tcpdf/include/barcodes/datamatrix.php and replace it with a
valid format such as "2010-2014, 2026" (or a single continuous range like
"2010-2026" if that's intended) so the year range/token is syntactically
correct.
In `@vendor/tecnickcom/tcpdf/include/barcodes/pdf417.php`:
- Line 10: Update the malformed copyright header string "2010-2013 2026" in the
file's top copyright comment to a valid format (for example "2010-2013, 2026")
so the year range is correctly punctuated; locate the copyright comment
containing "2010-2013 2026" and replace it with the corrected text.
In `@vendor/tecnickcom/tcpdf/include/tcpdf_fonts.php`:
- Line 1387: The checkSumAdjustment is currently written at the wrong offset in
the head table: update the write location so the packed 4-byte
checkSumAdjustment is inserted at +8..+12 instead of +4..+8; specifically modify
the expression that builds $font (the line using substr($font, 0,
$table['head']['offset'] + $offset + 4).pack('N',
$checkSumAdjustment).substr(...)) to use +8 (i.e. $table['head']['offset'] +
$offset + 8) for the start of the pack insertion so the packed value lands at
bytes +8..+12 relative to the head table.
In `@vendor/tecnickcom/tcpdf/include/tcpdf_images.php`:
- Line 277: Replace the repeated typo in the PNG diagnostic comments where the
message reads "Unknownn compression method" to the correct spelling "Unknown
compression method"; locate the occurrences of that string in tcpdf_images.php
(the PNG diagnostics branch handled in the image parsing logic) and update the
comment/text at each occurrence (previously at the lines containing "Unknownn
compression method") to the corrected form to ensure consistency.
In `@vendor/tecnickcom/tcpdf/tcpdf.php`:
- Around line 2909-2914: The setCompression method currently unconditionally
sets $this->compress = false and returns, making the rest of the function dead;
remove the early unconditional assignment and return so the method honors the
$compress parameter and the gzcompress availability check, i.e., in function
setCompression(...) use function_exists('gzcompress') and then set
$this->compress based on the incoming $compress value (and pdfa_mode if needed)
rather than always forcing false and returning early.
- Around line 23733-23737: The preg_split call that populates $rawparams
currently uses a capturing group, causing delimiters to be kept and skew
parameter positions; change the split to avoid capturing (use a non-capturing
group) or pass the PREG_SPLIT_NO_EMPTY flag so delimiters are not returned, then
remove the array_filter cleanup; update the preg_split invocation that fills
$rawparams (and subsequent handling of $rawparams) to rely on the cleaned tokens
only so numeric parameter indexing is correct.
- Around line 6925-6927: The fitBlock() bottom-clamp currently uses
$this->PageBreakTrigger + $this->bMargin which can push content into the footer
margin when fitonpage is true; change the clamp so $h is calculated as
$this->PageBreakTrigger - $y (i.e. $h = $this->PageBreakTrigger - $y) and then
recompute $w using the existing $ratio_wh logic (variables: fitBlock,
PageBreakTrigger, bMargin, fitonpage, $y, $h, $w, $ratio_wh).
---
Nitpick comments:
In `@app/Controllers/FrontendController.php`:
- Around line 1299-1322: The per-level BFS SQL loop using repeated prepares
(variables $queue, $visited and the descendant query) should be replaced by a
single SELECT id,parent_id FROM generi to load the tree into memory and then
perform the traversal in PHP (reusing or delegating to the existing
collectGenreTreeIds() helper) to collect the requested genre plus all
descendants; remove the repeated prepare/execute block and instead build an
adjacency map from the single result set and run a local BFS/DFS to populate
$visited (or return the id list) so you eliminate multiple DB round-trips and
centralize traversal logic.
In `@app/Models/BookRepository.php`:
- Around line 345-366: The repeated field-normalization block (using hasColumn,
addField, and validations for anno_pubblicazione and numero_pagine) should be
extracted into a single private helper (e.g., normalizeBookMetadata or
prepareBookFields) that accepts the incoming $data and either returns a
normalized map of field => [type, value] or accepts the $addField callable to
invoke directly; move the filter_var logic for anno_pubblicazione and
numero_pagine into that helper (preserve min_range validation), include the
string defaults for lingua/edizione/traduttore/illustratore, and then call this
helper from both create and update to iterate and apply addField so future field
changes stay in one place.
In `@app/Routes/web.php`:
- Around line 257-282: The current per-locale if/elseif block around $locale
with multiple calls to $registerRouteIfUnique for ProfileController->update
duplicates middleware and is unscalable; replace it by defining a centralized
map of locale => array of route aliases (e.g., $routeAliasesByLocale) and
iterate over that map to call $registerRouteIfUnique for each alias, reusing the
same middleware array [new CsrfMiddleware(), new
AuthMiddleware(['admin','staff','standard','premium'])] and the same controller
instantiation (ProfileController) and $db retrieval, so adding a new language
only requires updating the map instead of editing the if/elseif logic.
In `@app/Support/Updater.php`:
- Around line 748-751: Duplicate unauthenticated retry header arrays
($retryHeaders) are causing drift; create a single helper (e.g.,
getGitHubHeaders(bool $withAuth = false, ?string $token = null)) in Updater.php
that returns the canonical array including 'User-Agent' and 'Accept', and
conditionally adds an Authorization header when $withAuth is true, then replace
the inline $retryHeaders definitions (the occurrences around the current block
and the ones at the later occurrence) with calls to getGitHubHeaders(...) so all
retry paths use the same header set.
In `@tests/full-test.spec.js`:
- Line 49: Update the helper's comment to remove the misleading "injection-safe"
claim and clarify that it is safe from shell injection but not safe from SQL
injection; replace the phrase in the JSDoc on the helper in
tests/full-test.spec.js (the comment that begins "/** Execute a MySQL query and
return trimmed output (injection-safe, no shell). */") with wording such as
"shell-safe (does not use the shell), but not SQL-injection-safe — callers must
use parameterized queries or proper escaping when constructing SQL."
In `@vendor/slim/csrf/composer.json`:
- Around line 39-50: The composer.json in the vendored package incorrectly adds
project CI/test scripts ("sniffer:check", "sniffer:fix", "test", "check",
"test:coverage"); remove these script entries from
vendor/slim/csrf/composer.json and instead add the same script definitions to
your repository root composer.json "scripts" section so they persist across
dependency updates; update any script references (e.g., "@sniffer:check",
"@test:coverage") in the root file as needed and run composer dump-autoload /
composer update locally to ensure the root scripts are registered.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
composer.lockis excluded by!**/*.lock
📒 Files selected for processing (110)
.htaccessREADME.mdapp/Controllers/FrontendController.phpapp/Controllers/LibriController.phpapp/Controllers/UpdateController.phpapp/Models/BookRepository.phpapp/Routes/web.phpapp/Support/I18n.phpapp/Support/Updater.phpapp/Views/admin/updates.phpapp/Views/autori/index.phpapp/Views/editori/index.phpapp/Views/events/index.phpapp/Views/frontend/event-detail.phpapp/Views/frontend/events.phpapp/Views/frontend/home-sections/events.phpapp/Views/frontend/home-sections/genre_carousel.phpapp/Views/frontend/home.phpapp/Views/frontend/layout.phpapp/Views/layout.phpapp/Views/libri/index.phpapp/Views/libri/partials/book_form.phpapp/Views/libri/scheda_libro.phpapp/Views/partials/scroll-to-top.phpapp/Views/prestiti/index.phpapp/Views/utenti/index.phpfrontend/js/vendor.jsinstaller/classes/Installer.phpinstaller/database/data_de_DE.sqlinstaller/database/data_en_US.sqlinstaller/database/data_it_IT.sqlinstaller/index.phpinstaller/steps/step0.phplocale/de_DE.jsonlocale/routes_de_DE.jsonpublic/assets/main.csspublic/assets/vendor.bundle.jsscripts/.user.iniscripts/create-release.shscripts/manual-update.phpscripts/manual-upgrade.phptests/full-test.spec.jstests/genre-bugs.spec.jsvendor/composer/autoload_classmap.phpvendor/composer/autoload_files.phpvendor/composer/autoload_static.phpvendor/composer/installed.jsonvendor/composer/installed.phpvendor/graham-campbell/result-type/composer.jsonvendor/laravel/serializable-closure/composer.jsonvendor/laravel/serializable-closure/src/Serializers/Native.phpvendor/laravel/serializable-closure/src/Support/ReflectionClosure.phpvendor/monolog/monolog/README.mdvendor/monolog/monolog/composer.jsonvendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.phpvendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.phpvendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.phpvendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.phpvendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.phpvendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.phpvendor/monolog/monolog/src/Monolog/Handler/CubeHandler.phpvendor/monolog/monolog/src/Monolog/Handler/Curl/Util.phpvendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.phpvendor/monolog/monolog/src/Monolog/Handler/Handler.phpvendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.phpvendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.phpvendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.phpvendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.phpvendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.phpvendor/monolog/monolog/src/Monolog/Handler/StreamHandler.phpvendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.phpvendor/monolog/monolog/src/Monolog/Handler/TestHandler.phpvendor/slim/csrf/composer.jsonvendor/slim/psr7/composer.jsonvendor/slim/psr7/src/Cookies.phpvendor/slim/slim/MAINTAINERS.mdvendor/slim/slim/Slim/App.phpvendor/slim/slim/composer.jsonvendor/slim/slim/psalm.xmlvendor/tecnickcom/tcpdf/CHANGELOG.TXTvendor/tecnickcom/tcpdf/LICENSE.TXTvendor/tecnickcom/tcpdf/README.mdvendor/tecnickcom/tcpdf/VERSIONvendor/tecnickcom/tcpdf/composer.jsonvendor/tecnickcom/tcpdf/config/tcpdf_config.phpvendor/tecnickcom/tcpdf/fonts/ae_fonts_2.0/READMEvendor/tecnickcom/tcpdf/fonts/freefont-20100919/COPYINGvendor/tecnickcom/tcpdf/fonts/freefont-20100919/CREDITSvendor/tecnickcom/tcpdf/fonts/freefont-20120503/COPYINGvendor/tecnickcom/tcpdf/fonts/freefont-20120503/CREDITSvendor/tecnickcom/tcpdf/fonts/freefont-20120503/READMEvendor/tecnickcom/tcpdf/fonts/freefont-20120503/TROUBLESHOOTINGvendor/tecnickcom/tcpdf/include/barcodes/datamatrix.phpvendor/tecnickcom/tcpdf/include/barcodes/pdf417.phpvendor/tecnickcom/tcpdf/include/barcodes/qrcode.phpvendor/tecnickcom/tcpdf/include/tcpdf_colors.phpvendor/tecnickcom/tcpdf/include/tcpdf_filters.phpvendor/tecnickcom/tcpdf/include/tcpdf_font_data.phpvendor/tecnickcom/tcpdf/include/tcpdf_fonts.phpvendor/tecnickcom/tcpdf/include/tcpdf_images.phpvendor/tecnickcom/tcpdf/include/tcpdf_static.phpvendor/tecnickcom/tcpdf/tcpdf.phpvendor/tecnickcom/tcpdf/tcpdf_autoconfig.phpvendor/tecnickcom/tcpdf/tcpdf_barcodes_1d.phpvendor/tecnickcom/tcpdf/tcpdf_barcodes_2d.phpvendor/tecnickcom/tcpdf/tools/tcpdf_addfont.phpvendor/vlucas/phpdotenv/composer.jsonvendor/vlucas/phpdotenv/src/Parser/EntryParser.phpvendor/vlucas/phpdotenv/src/Repository/Adapter/EnvConstAdapter.phpvendor/vlucas/phpdotenv/src/Repository/Adapter/ServerConstAdapter.php
💤 Files with no reviewable changes (7)
- vendor/vlucas/phpdotenv/src/Parser/EntryParser.php
- vendor/vlucas/phpdotenv/src/Repository/Adapter/ServerConstAdapter.php
- vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php
- vendor/vlucas/phpdotenv/src/Repository/Adapter/EnvConstAdapter.php
- scripts/manual-update.php
- vendor/slim/slim/MAINTAINERS.md
- vendor/slim/slim/psalm.xml
- Updater: treat non-2xx file_get_contents download as failure - Installer: fix IPv6 port fallback (separate if block for forwarded/server port) - BookRepository: validate numero_pagine in updateOptionals with filter_var - scroll-to-top: extract updateVisibility function and call on page load - manual-upgrade: add CSRF token protection for upgrade POST - tests: fix dbQuery comment (shell-safe, not injection-safe)
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php (1)
1-280:⚠️ Potential issue | 🔴 CriticalDo not modify vendor files directly.
This file belongs to the
monolog/monologComposer package. Direct modifications to vendor files:
- Will be overwritten on the next
composer updateorcomposer install- Break the dependency management workflow and reproducibility
- Are unrelated to this PR's stated objectives (German locale, dynamic routes, GitHub token, manual upgrade)
If log rotation inode detection is needed, create a custom handler that extends
StreamHandlerin your application code instead:// app/Support/Logging/RotationAwareStreamHandler.php namespace App\Support\Logging; use Monolog\Handler\StreamHandler; use Monolog\LogRecord; class RotationAwareStreamHandler extends StreamHandler { private ?int $cachedInode = null; protected function write(LogRecord $record): void { // Custom inode detection logic here parent::write($record); } }Alternatively, consider using Monolog's built-in
RotatingFileHandlerwhich handles log rotation natively.Please revert this vendor file modification and implement the functionality properly in application code.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php` around lines 1 - 280, The PR directly modified a vendor file (Monolog\Handler\StreamHandler) which must not be changed; revert the vendor edits and implement the inode-aware behavior in app code instead. Revert changes to StreamHandler in vendor, then add a new class (e.g. App\Support\Logging\RotationAwareStreamHandler) that extends Monolog\Handler\StreamHandler and override the protected write(LogRecord $record) method to implement your log-rotation/inode detection using or delegating to the existing private helpers (mirror behavior of getInodeFromUrl and hasUrlInodeWasChanged logic in your subclass) or simply switch to Monolog\Handler\RotatingFileHandler if that meets requirements; ensure all references use the new RotationAwareStreamHandler in your logging config.vendor/slim/psr7/composer.json (1)
1-73:⚠️ Potential issue | 🔴 CriticalRemove the entire
vendor/directory from git tracking.The vendor directory is currently being tracked by git (2,400+ files), which violates standard dependency management practices. Update
.gitignoreto include:/vendor/The current
.gitignoreonly excludes specific files within vendor directories but does not prevent vendor files from being tracked. Vendor files should be regenerated locally viacomposer installbased oncomposer.lock, not committed to the repository.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/slim/psr7/composer.json` around lines 1 - 73, The repo is tracking the vendor/ directory; stop tracking it and add a gitignore rule. Add "/vendor/" to .gitignore, remove the tracked files from the index (git rm -r --cached vendor) and commit that change so vendor/ is no longer in git history going forward, and ensure consumers regenerate dependencies with composer install using composer.lock and composer.json (refer to composer.json in the diff to confirm dependency management).vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php (1)
226-233:⚠️ Potential issue | 🟠 Major
getNextRotation()ignores the configured timezone.
getTimedFilename()correctly uses$this->timezone(line 183), butgetNextRotation()createsDateTimeImmutableobjects without passing the timezone. This means rotation boundaries are computed in the server's default timezone while filenames use the configured timezone, leading to incorrect rotation timing.Suggested fix
protected function getNextRotation(): \DateTimeImmutable { + $now = new \DateTimeImmutable('now', $this->timezone); return match (str_replace(['/','_','.'], '-', $this->dateFormat)) { - self::FILE_PER_MONTH => (new \DateTimeImmutable('first day of next month'))->setTime(0, 0, 0), - self::FILE_PER_YEAR => (new \DateTimeImmutable('first day of January next year'))->setTime(0, 0, 0), - default => (new \DateTimeImmutable('tomorrow'))->setTime(0, 0, 0), + self::FILE_PER_MONTH => $now->modify('first day of next month')->setTime(0, 0, 0), + self::FILE_PER_YEAR => $now->modify('first day of January next year')->setTime(0, 0, 0), + default => $now->modify('tomorrow')->setTime(0, 0, 0), }; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php` around lines 226 - 233, getNextRotation() builds DateTimeImmutable instances without using the configured timezone, causing rotation times to differ from getTimedFilename(); update getNextRotation() to construct the DateTimeImmutable objects with $this->timezone (e.g. new \DateTimeImmutable('first day of next month', $this->timezone)) before calling setTime so FILE_PER_MONTH, FILE_PER_YEAR and default branch use the same timezone as getTimedFilename().app/Views/frontend/events.php (1)
360-380:⚠️ Potential issue | 🟠 MajorUse localized route helper for event links instead of hardcoded
/events/.Line 360, Line 374, and Line 379 hardcode the path and directly concatenate the slug. This bypasses translated routes and can break locale-specific URLs.
🔧 Proposed fix
- <a href="<?= htmlspecialchars(url('/events/' . $event['slug']), ENT_QUOTES, 'UTF-8') ?>" class="event-card__thumb"> + <a href="<?= htmlspecialchars(route_path('events') . '/' . rawurlencode($event['slug']), ENT_QUOTES, 'UTF-8') ?>" class="event-card__thumb"> @@ - <a href="<?= htmlspecialchars(url('/events/' . $event['slug']), ENT_QUOTES, 'UTF-8') ?>"> + <a href="<?= htmlspecialchars(route_path('events') . '/' . rawurlencode($event['slug']), ENT_QUOTES, 'UTF-8') ?>"> @@ - <a href="<?= htmlspecialchars(url('/events/' . $event['slug']), ENT_QUOTES, 'UTF-8') ?>" class="event-card__button"> + <a href="<?= htmlspecialchars(route_path('events') . '/' . rawurlencode($event['slug']), ENT_QUOTES, 'UTF-8') ?>" class="event-card__button">🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Views/frontend/events.php` around lines 360 - 380, The links currently build URLs by concatenating '/events/' with $event['slug'] using url('/events/' . $event['slug']) which bypasses translated/localized routes; replace those occurrences (the three url(...) calls used for the thumbnail link, title link, and action button) with your app's localized/named route helper (e.g., route('events.show', ['slug' => $event['slug']]) or the project-specific localized_route equivalent) so locale-aware URLs are generated while keeping HtmlHelper::e(...) and the translation call __("Scopri l'evento") intact.app/Views/layout.php (1)
1491-1535:⚠️ Potential issue | 🟡 MinorReset pending
gcombo state on Escape.After pressing
g, hitting Escape does not cleargPrefixActive; a subsequent key can still trigger navigation. Line 1492 branch should reset the combo state.🩹 Proposed fix
if (e.key === 'Escape') { + gPrefixActive = false; + clearTimeout(gPrefixTimer); + // Close shortcuts modal closeShortcutsModal();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Views/layout.php` around lines 1491 - 1535, The Escape key handler fails to reset the pending 'g' combo state, so update the block handling e.key === 'Escape' to clear that state: set gPrefixActive = false, clear and null-out any timer variable used for the combo (e.g., gPrefixTimer) and remove any UI indicator tied to the prefix if present (e.g., a CSS class or element shown for the 'g' prefix). Locate the variables/handlers named gPrefixActive and gPrefixTimer (or similar) in the surrounding code, call clearTimeout(gPrefixTimer) if defined, set gPrefixTimer = null, and ensure gPrefixActive = false inside the Escape branch so subsequent keys won't trigger navigation.
♻️ Duplicate comments (11)
vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php (1)
46-49:⚠️ Potential issue | 🟠 MajorRevert direct edits under
vendor/and keep Monolog Composer-managed.Line 46 and Line 49 are edits inside a vendored dependency (
monolog/monolog). These changes are not durable and will be overwritten bycomposer install/update. Please revert this file in-repo and apply the change via either an upstream PR or a Composer patch workflow.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php` around lines 46 - 49, This file shows local edits to the vendored Monolog class ProcessHandler (e.g., the private property $timeout and nearby docblock changes) which must not be modified directly; revert any changes made under vendor/monolog (restore the original committed vendor file or run composer install to reset it) and instead implement the intended fix by creating an upstream PR to monolog/monolog or by applying the change via a Composer patch (e.g., using cweagans/composer-patches) so the modification to ProcessHandler is durable and managed by Composer.app/Models/BookRepository.php (1)
965-967:⚠️ Potential issue | 🟠 MajorValidate
anno_pubblicazioneinupdateOptionals()before integer cast.
anno_pubblicazionestill enters$colsas raw input and is later cast with(int), so invalid non-empty values (e.g."abc") can become0silently.🔧 Proposed fix
foreach (['numero_pagine', 'ean', 'data_pubblicazione', 'anno_pubblicazione', 'traduttore', 'illustratore', 'collana', 'edizione'] as $c) { if ($this->hasColumn($c) && array_key_exists($c, $data) && $data[$c] !== '' && $data[$c] !== null) { if ($c === 'numero_pagine') { $validated = filter_var($data[$c], FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]); if ($validated !== false) { $cols[$c] = $validated; } + } elseif ($c === 'anno_pubblicazione') { + $validated = filter_var($data[$c], FILTER_VALIDATE_INT); + if ($validated !== false) { + $cols[$c] = $validated; + } } else { $cols[$c] = $data[$c]; } } }Also applies to: 999-1002
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Models/BookRepository.php` around lines 965 - 967, In updateOptionals() validate the anno_pubblicazione input before it is placed into $cols to avoid non-numeric strings being silently cast to 0; specifically, before assigning $cols['anno_pubblicazione'] (or importing via the generic $cols[$c] = $data[$c] branch) check that $data['anno_pubblicazione'] is a valid integer string (e.g. ctype_digit or filter_var with FILTER_VALIDATE_INT and optional range check) and only set $cols['anno_pubblicazione'] = (int)$data['anno_pubblicazione'] when valid, otherwise skip setting or set null/error; apply the same validation fixes to the similar handling around the other block (lines 999-1002) that also casts anno_pubblicazione.vendor/laravel/serializable-closure/src/Support/ReflectionClosure.php (1)
1363-1387:⚠️ Potential issue | 🟠 Major
verifyCandidateSignature()still misparses nested parentheses in parameter parsing.At Line 1385, any
)while inparamsflips tobody. Nested parentheses inside parameter expressions can prematurely end parameter parsing and break candidate matching.🐛 Proposed fix
protected function verifyCandidateSignature($candidate) { $code = $candidate['code']; $use = $candidate['use']; $isShortClosure = $candidate['isShortClosure']; @@ $tokens = token_get_all('<?php '.$code); $params = []; $vars = []; $state = 'start'; + $depth = 0; foreach ($tokens as $token) { if (! is_array($token)) { - if ($token === '(' && $state === 'start') { - $state = 'params'; - } elseif ($token === ')' && $state === 'params') { - $state = 'body'; + if ($token === '(') { + if ($state === 'start') { + $state = 'params'; + $depth = 1; + } elseif ($state === 'params') { + $depth++; + } + } elseif ($token === ')' && $state === 'params') { + $depth--; + if ($depth === 0) { + $state = 'body'; + } } continue; }#!/bin/bash set -euo pipefail file="vendor/laravel/serializable-closure/src/Support/ReflectionClosure.php" echo "=== verifyCandidateSignature token-state block ===" nl -ba "$file" | sed -n '1368,1402p' echo echo "=== Direct params->body transition on ')' ===" rg -n "\$token === '\)' && \$state === 'params'|\$state = 'body'" "$file" echo echo "=== Depth tracking variables in verifyCandidateSignature ===" if rg -n "\$depth|\$parenDepth|\$parenthesisDepth" "$file"; then echo "Depth tracking present." else echo "No depth tracking variable found." fi🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/laravel/serializable-closure/src/Support/ReflectionClosure.php` around lines 1363 - 1387, The parameter parser in verifyCandidateSignature incorrectly transitions from params to body on any ')' token, which breaks on nested parentheses; fix it by introducing a parentheses depth counter (e.g., $parenDepth) inside the foreach over $tokens and increment when encountering '(' and decrement on ')' while in the 'params' state, only flip $state to 'body' when $state === 'params' AND $parenDepth returns to zero (handle non-array tokens and ignore paren changes outside 'params'); update references in the tokens loop and any logic that tests $state so nested parameter expressions are parsed correctly.vendor/tecnickcom/tcpdf/include/barcodes/datamatrix.php (1)
10-10:⚠️ Potential issue | 🟡 MinorFix malformed copyright year range on Line 10.
Use a single valid range;
2010-2014 2026is malformed.Suggested fix
-// Copyright (C) 2010-2014 2026 Nicola Asuni - Tecnick.com LTD +// Copyright (C) 2010-2026 Nicola Asuni - Tecnick.com LTD🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/include/barcodes/datamatrix.php` at line 10, The copyright header string "Copyright (C) 2010-2014 2026 Nicola Asuni - Tecnick.com LTD" is malformed; replace the duplicate year tokens with a single valid range, e.g. change that header to "Copyright (C) 2010-2026 Nicola Asuni - Tecnick.com LTD" so the years form one continuous range and the header is syntactically correct.vendor/tecnickcom/tcpdf/include/barcodes/pdf417.php (1)
10-10:⚠️ Potential issue | 🟡 MinorFix malformed copyright year formatting on Line 10.
2010-2013 2026is malformed and should be a single coherent range.Proposed fix
-// Copyright (C) 2010-2013 2026 Nicola Asuni - Tecnick.com LTD +// Copyright (C) 2010-2026 Nicola Asuni - Tecnick.com LTD🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/include/barcodes/pdf417.php` at line 10, The copyright header string "Copyright (C) 2010-2013 2026 Nicola Asuni - Tecnick.com LTD" is malformed; replace it with a single coherent year range such as "Copyright (C) 2010-2026 Nicola Asuni - Tecnick.com LTD" by editing the header line in vendor/tecnickcom/tcpdf/include/barcodes/pdf417.php (the line containing that copyright string) so the years are a single contiguous range.vendor/tecnickcom/tcpdf/include/tcpdf_images.php (1)
277-277:⚠️ Potential issue | 🟡 MinorFix diagnostic typo regressions in PNG parser comments.
Unknownnappears in three comments and should be corrected toUnknown.Proposed fix
- // Unknownn compression method + // Unknown compression method ... - // Unknownn filter method + // Unknown filter method ... - // Unknownn filter method + // Unknown compression methodAlso applies to: 282-282, 334-334
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/include/tcpdf_images.php` at line 277, There are three comment typos in tcpdf_images.php where "Unknownn" is misspelled; locate occurrences of the string "Unknownn" (e.g., the comment "Unknownn compression method") and correct them to "Unknown" in the PNG parser comments so all three instances read "Unknown" (search for "Unknownn" to find and replace in the relevant comment blocks).vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php (1)
310-310:⚠️ Potential issue | 🟠 MajorAvoid normalizing the entire stacktrace string.
Line 310 still normalizes the whole trace text, which can rewrite namespace separators in class names on Windows. Normalize only path fragments, not full frames.
🔧 Suggested fix
- return "\n{$this->indentStacktraces}[stacktrace]\n{$this->indentStacktraces}" . strtr($trace, DIRECTORY_SEPARATOR, '/') . "\n"; + $normalizedTrace = preg_replace_callback( + '/^(#\d+\s+)(.+?)(\(\d+\):)/m', + static fn (array $m): string => $m[1] . str_replace(DIRECTORY_SEPARATOR, '/', $m[2]) . $m[3], + $trace + ) ?? $trace; + + return "\n{$this->indentStacktraces}[stacktrace]\n{$this->indentStacktraces}{$normalizedTrace}\n";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php` at line 310, The current return normalizes the entire $trace string with strtr which also alters namespace separators on Windows; change the logic in LineFormatter (the method that builds this return using $this->indentStacktraces and $trace) to only normalize filesystem path fragments inside each stack frame instead of the whole $trace (e.g. detect file path segments within frames and replace DIRECTORY_SEPARATOR with '/' only for those matches), preserving backslashes used in PHP namespaces and class names.vendor/tecnickcom/tcpdf/include/tcpdf_fonts.php (1)
1387-1387:⚠️ Potential issue | 🔴 CriticalRestore
checkSumAdjustmentwrite position tohead+8.Line 1387 writes checksum bytes at
+4..+8, which targets the wrongheadfield. This should be+8..+12; otherwise subsetted TTF metadata is corrupted.🐛 Proposed fix
-$font = substr($font, 0, $table['head']['offset'] + $offset + 4).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + $offset + 8); +$font = substr($font, 0, $table['head']['offset'] + $offset + 8).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + $offset + 12);#!/bin/bash # Verify where checkSumAdjustment is written in TCPDF_FONTS::_getTrueTypeFontSubset rg -n "set checkSumAdjustment|pack\('N', \$checkSumAdjustment\)" vendor/tecnickcom/tcpdf/include/tcpdf_fonts.php -C2 python - <<'PY' import re, pathlib p = pathlib.Path("vendor/tecnickcom/tcpdf/include/tcpdf_fonts.php") t = p.read_text() m = re.search(r"\$font = substr\(\$font, 0, \$table\['head'\]\['offset'\] \+ \$offset \+ (\d+)\)\.pack\('N', \$checkSumAdjustment\)\.substr\(\$font, \$table\['head'\]\['offset'\] \+ \$offset \+ (\d+)\);", t) print("found offsets:", m.groups() if m else "pattern not found") print("expected offsets: ('8', '12')") PY🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/include/tcpdf_fonts.php` at line 1387, In TCPDF_FONTS::_getTrueTypeFontSubset the checksum bytes are written at the wrong position: the current expression uses $table['head']['offset'] + $offset + 4 and +8, corrupting subsetted TTF metadata; update the write position to use +8 and +12 so the pack('N', $checkSumAdjustment) replaces bytes at head+8..+11 (i.e. change both occurrences of "+ 4" -> "+ 8" and "+ 8" -> "+ 12" around the substr/pack that writes $checkSumAdjustment for the $font buffer).scripts/manual-upgrade.php (1)
298-300:⚠️ Potential issue | 🟠 MajorCast CSRF token and session values to strings before
hash_equals()to prevent fatal TypeError.Lines 298-300 contain CSRF validation outside the try/catch block.
$_POST['csrf_token']is not type-normalized. If an attacker sendscsrf_token[]=x, the null coalescing operator returns an array instead of a string. In PHP 8.1 (required by this project),hash_equals()is strictly typed and throws a fatalTypeErrorwhen given non-string arguments. This exception occurs before the try/catch block (which starts at line 307), resulting in an unhandled 500 error instead of a graceful CSRF rejection.Cast both values to strings:
Suggested patch
- $submittedToken = $_POST['csrf_token'] ?? ''; - if (!hash_equals($_SESSION['upgrade_csrf'] ?? '', $submittedToken)) { + $submittedToken = (string) ($_POST['csrf_token'] ?? ''); + $sessionToken = (string) ($_SESSION['upgrade_csrf'] ?? ''); + if (!hash_equals($sessionToken, $submittedToken)) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/manual-upgrade.php` around lines 298 - 300, The CSRF check uses hash_equals() with unvalidated inputs which can be arrays and cause a TypeError; convert both operands to strings before calling hash_equals so it never receives non-string types (cast the $_POST['csrf_token'] result into a string for $submittedToken and cast the $_SESSION['upgrade_csrf'] fallback to string when passed to hash_equals) while preserving the existing logic and error assignment in the block that performs the comparison.vendor/tecnickcom/tcpdf/config/tcpdf_config.php (1)
11-11:⚠️ Potential issue | 🟡 MinorPossible malformed TCPDF metadata/header text in vendored file.
Line 11 (
2004-2014 2026) and Line 117 (by2026 ...) look corrupted. If this differs from upstream 6.11.0, PDFs may show unintended header text. This matches a previously raised concern.#!/bin/bash set -euo pipefail TAG="6.11.0" UPSTREAM_URL="https://raw.githubusercontent.com/tecnickcom/TCPDF/${TAG}/config/tcpdf_config.php" echo "=== Upstream (${TAG}) relevant lines ===" curl -fsSL "$UPSTREAM_URL" | sed -n '9,13p;115,118p' echo echo "=== Local relevant lines ===" sed -n '9,13p;115,118p' vendor/tecnickcom/tcpdf/config/tcpdf_config.phpExpected result: local lines should match upstream exactly. If they do not, sync the vendored files to the official release artifacts.
Also applies to: 117-117
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/tecnickcom/tcpdf/config/tcpdf_config.php` at line 11, The vendored TCPDF config file contains corrupted header metadata (e.g., the strings "2004-2014 2026" and "by2026" in vendor/tecnickcom/tcpdf/config/tcpdf_config.php) that likely differs from upstream 6.11.0; replace or restore these header lines to exactly match the official upstream release (tag 6.11.0) by fetching the canonical file from https://raw.githubusercontent.com/tecnickcom/TCPDF/6.11.0/config/tcpdf_config.php and syncing the vendored file so the header/comment text and any other differing lines match upstream exactly.public/assets/main.css (1)
4662-4665:⚠️ Potential issue | 🟡 MinorStylelint
declaration-empty-line-beforeviolations still present in newly added utility blocks.These blocks still violate the lint rule and can keep CI red. Please regenerate this CSS or run auto-fix so declaration spacing matches repo formatting.
🔧 Example fix pattern
.border-amber-300 { --tw-border-opacity: 1; + border-color: rgb(252 211 77 / var(--tw-border-opacity, 1)); }Also applies to: 4686-4689, 4853-4868, 4891-4894, 4950-4973, 5460-5499, 5528-5531, 5556-5559, 5623-5626, 6398-6401, 6450-6453, 6490-6493, 6506-6517, 6526-6529, 6574-6577, 6602-6605, 6798-6801, 6858-6864, 6903-6909, 6996-6999, 7048-7071
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@public/assets/main.css` around lines 4662 - 4665, The added utility blocks (for example the .border-amber-300 block and the other newly added classes noted in the comment) violate the stylelint rule declaration-empty-line-before; to fix, regenerate the CSS or run the repository's Stylelint autofix (e.g., stylelint --fix) so declaration spacing matches the repo formatting, or manually adjust each utility block to insert/remove the required empty line before declarations to match the existing pattern; search for the class identifiers like ".border-amber-300" to locate the offending blocks and re-run the build/asset generation to ensure all ranges listed are corrected.
🧹 Nitpick comments (8)
vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php (1)
162-172: Directory cleanup path comparison may be fragile.The comparison
$dir !== $basePath(line 164) uses strict string inequality. If paths are canonicalized differently (symlinks, relative vs absolute, trailing slashes), the loop could either:
- Stop prematurely (no harm)
- Continue beyond intended scope (unlikely given
rmdirfails on non-empty dirs, but still a risk)Consider using
realpath()for comparison or adding a depth limit as a safeguard.Suggested improvement
$basePath = dirname($this->filename); + $basePath = realpath($basePath) ?: $basePath; foreach (\array_slice($logFiles, $this->maxFiles) as $file) { if (is_writable($file)) { // suppress errors here as unlink() might fail if two processes // are cleaning up/rotating at the same time set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline): bool { return true; }); unlink($file); $dir = dirname($file); + $dir = realpath($dir) ?: $dir; while ($dir !== $basePath) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php` around lines 162 - 172, The directory cleanup loop in RotatingFileHandler that compares $dir !== $basePath is fragile to path canonicalization differences; update the loop to compare normalized real paths (e.g., compute $dirReal = realpath($dir) and $baseReal = realpath($basePath) or normalize with rtrim and DIRECTORY_SEPARATOR) and use those for the equality check, and add a simple depth/safety counter (max iterations) as a fallback to prevent accidental traversal beyond the intended base; locate the loop using the $dir and $basePath variables in the RotatingFileHandler cleanup logic and replace the comparison and loop guard accordingly.tests/full-test.spec.js (1)
167-2359: Consider splitting this suite into smaller spec files by phase.The single-file, cross-phase shared state design makes triage and reruns expensive when one early phase fails. Breaking phases into smaller specs with scoped fixtures will improve maintainability and failure isolation.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/full-test.spec.js` around lines 167 - 2359, Single-line: The huge all-phases single spec file makes failures hard to triage; split phases into smaller spec files and replace global shared state with scoped fixtures/helpers. Create separate spec files per phase (e.g. phase1.install.spec.js, phase2.login.spec.js) by extracting each test.describe.serial('Phase X: ...') block into its own file, move shared helpers (loginAsAdmin, dbQuery, requestLoanViaSwal, dismissSwal, escapeSqlLike, RUN_ID, state) into a tests/utils.js or into Playwright fixtures, and implement fixtures (using test.extend) to provide per-spec scoped state (createdBookIds, authorIds, userId, eventId) and common setup/teardown so tests no longer rely on a single global state object; ensure cleanup logic from the Phase 20 block is adapted into fixture teardown or per-spec cleanup to keep tests isolated and rerunnable.app/Views/events/index.php (1)
36-40: Consider centralizing date/time fallback formatting in a shared helper.This logic now appears in multiple views; extracting it would reduce drift and make locale fallback changes easier to maintain.
Also applies to: 42-55, 57-70
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Views/events/index.php` around lines 36 - 40, The date fallback logic duplicated as $fallbackDateFormat = match (strtolower(substr($locale, 0, 2))) { ... } should be extracted into a single shared helper (e.g., getFallbackDateFormat($locale) in a view/helper class or global helper) and all occurrences (including the other blocks mentioned) should call that helper instead of reimplementing the match; implement getFallbackDateFormat to normalize locale (lowercase + substr first 2 chars) and return the same mappings for 'de' and 'it' with the default, then update the views to use the helper (replace uses of $fallbackDateFormat with the helper call) and register/import the helper where needed.app/Views/layout.php (1)
1412-1420: Prefer route-driven shortcuts instead of hardcoded URLs.Hardcoded destinations can drift from real nav links/routes over time. Consider building
gNavMapfrom existing nav anchors (or server-rendered route values) to keep shortcuts aligned automatically.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Views/layout.php` around lines 1412 - 1420, Replace the hardcoded gNavMap object with values derived from actual route anchors or server-rendered route variables so shortcuts stay in sync: locate the gNavMap declaration and basePath usage in layout.php, query the DOM for the admin nav anchors (e.g., document.querySelectorAll on the admin nav links) or consume server-provided route values (e.g., a routes array injected into the template), and build gNavMap by mapping the shortcut keys ('d','b','a','e','p','u','s') to the hrefs from those anchors or route values instead of concatenating basePath + literal strings.app/Views/admin/updates.php (1)
562-632: Deduplicate token mutation flow to reduce maintenance drift.
saveGitHubToken()andremoveGitHubToken()duplicate most of the same request/result/error handling. Consider extracting a single helper for mutation + toast + reload flow.♻️ Proposed refactor
+async function mutateGitHubToken(tokenValue, successTitle) { + const data = await postTokenRequest(tokenValue); + if (data.success) { + await Swal.fire({ + icon: 'success', + title: successTitle, + text: data.message, + timer: 1500, + showConfirmButton: false + }); + location.reload(); + return; + } + throw new Error(data.error || 'Operazione non riuscita'); +} + async function saveGitHubToken() { @@ - try { - const data = await postTokenRequest(token); - - if (data.success) { - Swal.fire({ - icon: 'success', - title: <?= json_encode(__("Salvato"), JSON_HEX_TAG) ?>, - text: data.message, - timer: 1500, - showConfirmButton: false - }).then(() => location.reload()); - } else { - Swal.fire({ icon: 'error', title: <?= json_encode(__("Errore"), JSON_HEX_TAG) ?>, text: data.error }); - } - } catch (error) { + try { + await mutateGitHubToken(token, <?= json_encode(__("Salvato"), JSON_HEX_TAG) ?>); + } catch (error) { Swal.fire({ icon: 'error', title: <?= json_encode(__("Errore"), JSON_HEX_TAG) ?>, text: error.message }); } finally { tokenRequestInFlight = false; } } @@ - try { - const data = await postTokenRequest(''); - - if (data.success) { - Swal.fire({ - icon: 'success', - title: <?= json_encode(__("Rimosso"), JSON_HEX_TAG) ?>, - text: data.message, - timer: 1500, - showConfirmButton: false - }).then(() => location.reload()); - } else { - Swal.fire({ icon: 'error', title: <?= json_encode(__("Errore"), JSON_HEX_TAG) ?>, text: data.error }); - } - } catch (error) { + try { + await mutateGitHubToken('', <?= json_encode(__("Rimosso"), JSON_HEX_TAG) ?>); + } catch (error) { Swal.fire({ icon: 'error', title: <?= json_encode(__("Errore"), JSON_HEX_TAG) ?>, text: error.message }); } finally { tokenRequestInFlight = false; } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Views/admin/updates.php` around lines 562 - 632, saveGitHubToken and removeGitHubToken duplicate the same request/result/error/toast/reload flow; extract a helper (e.g., performTokenMutation(token)) that checks and sets tokenRequestInFlight, calls postTokenRequest(token), shows success/error Swal messages and triggers location.reload() on success, and resets tokenRequestInFlight in a finally block; keep callers (saveGitHubToken: read input value and validate; removeGitHubToken: show confirmation) but replace their request logic with a single call to performTokenMutation('token' or '') to avoid duplication and maintenance drift.app/Routes/web.php (2)
93-123: Reduce duplicated events handlers by reusing shared closures.The same handler logic is repeated for fallback and localized variants, which increases maintenance cost.
♻️ Proposed refactor
- $registerRouteIfUnique('GET', '/events', function ($request, $response) use ($app) { + $eventsListHandler = function ($request, $response) use ($app) { $container = $app->getContainer(); $controller = new \App\Controllers\FrontendController($container); $db = $container->get('db'); return $controller->events($request, $response, $db); - }); - $registerRouteIfUnique('GET', '/events/{slug}', function ($request, $response, $args) use ($app) { + }; + $eventDetailHandler = function ($request, $response, $args) use ($app) { $container = $app->getContainer(); $controller = new \App\Controllers\FrontendController($container); $db = $container->get('db'); return $controller->event($request, $response, $db, $args); - }); + }; + + $registerRouteIfUnique('GET', '/events', $eventsListHandler); + $registerRouteIfUnique('GET', '/events/{slug}', $eventDetailHandler); @@ - $registerRouteIfUnique('GET', RouteTranslator::getRouteForLocale('events', $locale), function ($request, $response) use ($app) { - $container = $app->getContainer(); - $controller = new \App\Controllers\FrontendController($container); - $db = $container->get('db'); - return $controller->events($request, $response, $db); - }); + $registerRouteIfUnique('GET', RouteTranslator::getRouteForLocale('events', $locale), $eventsListHandler); @@ - $registerRouteIfUnique('GET', RouteTranslator::getRouteForLocale('events', $locale) . '/{slug}', function ($request, $response, $args) use ($app) { - $container = $app->getContainer(); - $controller = new \App\Controllers\FrontendController($container); - $db = $container->get('db'); - return $controller->event($request, $response, $db, $args); - }); + $registerRouteIfUnique('GET', RouteTranslator::getRouteForLocale('events', $locale) . '/{slug}', $eventDetailHandler);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Routes/web.php` around lines 93 - 123, Duplicate route handlers for events can be replaced by shared closures to avoid repetition: create a single $eventsHandler closure that instantiates new \App\Controllers\FrontendController($container) and calls its events($request,$response,$db), and a single $eventHandler closure that calls event($request,$response,$db,$args); then use those two closures with $registerRouteIfUnique for both the English fallback routes and inside the foreach over $supportedLocales using RouteTranslator::getRouteForLocale('events', $locale) (and the '/{slug}' variant) so you reuse the same handler logic instead of duplicating it.
257-281: Make hybrid profile-update aliases data-driven.The locale-specific
if/elseifchain will keep growing as new languages are added via Admin → Languages.♻️ Proposed refactor
- if ($locale === 'it_IT') { - // Italian base + English action - $registerRouteIfUnique('POST', '/profilo/update', function ($request, $response) use ($app) { - $db = $app->getContainer()->get('db'); - $controller = new ProfileController(); - return $controller->update($request, $response, $db); - }, [new CsrfMiddleware(), new AuthMiddleware(['admin', 'staff', 'standard', 'premium'])]); - } elseif ($locale === 'en_US') { - // English base + Italian action - $registerRouteIfUnique('POST', '/profile/aggiorna', function ($request, $response) use ($app) { - $db = $app->getContainer()->get('db'); - $controller = new ProfileController(); - return $controller->update($request, $response, $db); - }, [new CsrfMiddleware(), new AuthMiddleware(['admin', 'staff', 'standard', 'premium'])]); - } elseif ($locale === 'de_DE') { - // German base + English/Italian action - $registerRouteIfUnique('POST', '/profil/update', function ($request, $response) use ($app) { - $db = $app->getContainer()->get('db'); - $controller = new ProfileController(); - return $controller->update($request, $response, $db); - }, [new CsrfMiddleware(), new AuthMiddleware(['admin', 'staff', 'standard', 'premium'])]); - $registerRouteIfUnique('POST', '/profil/aggiorna', function ($request, $response) use ($app) { - $db = $app->getContainer()->get('db'); - $controller = new ProfileController(); - return $controller->update($request, $response, $db); - }, [new CsrfMiddleware(), new AuthMiddleware(['admin', 'staff', 'standard', 'premium'])]); - } + $hybridProfileAliases = [ + 'it_IT' => ['/profilo/update'], + 'en_US' => ['/profile/aggiorna'], + 'de_DE' => ['/profil/update', '/profil/aggiorna'], + ]; + foreach ($hybridProfileAliases[$locale] ?? [] as $aliasPath) { + $registerRouteIfUnique('POST', $aliasPath, function ($request, $response) use ($app) { + $db = $app->getContainer()->get('db'); + $controller = new ProfileController(); + return $controller->update($request, $response, $db); + }, [new CsrfMiddleware(), new AuthMiddleware(['admin', 'staff', 'standard', 'premium'])]); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Routes/web.php` around lines 257 - 281, The repeated locale-specific if/elseif blocks registering POST profile update routes should be replaced with a data-driven mapping: create a map (e.g., $localeToPaths) keyed by $locale with arrays of URI aliases, then loop over $localeToPaths[$locale] and call $registerRouteIfUnique for each path; reuse the same closure that resolves $db and instantiates ProfileController and keep the same middleware list ([new CsrfMiddleware(), new AuthMiddleware(['admin','staff','standard','premium'])]) so you remove hard-coded branches (the blocks using registerRouteIfUnique, ProfileController::update, CsrfMiddleware, AuthMiddleware) and make adding new language aliases a simple data change.app/Support/Updater.php (1)
748-760: Deduplicate anonymous-header construction.Anonymous retry headers are manually duplicated in two places. Consider a small helper (or a
getGitHubHeaders(..., withAuth:false)pattern) to keep behavior aligned.Also applies to: 948-951
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Support/Updater.php` around lines 748 - 760, The duplicate anonymous header array ($retryHeaders) used when building curl requests (seen around $retryHeaders and the curl_setopt_array for $ch2) should be centralized: add a small helper method (e.g., getGitHubHeaders(bool $withAuth = true) or getGitHubRetryHeaders()) in Updater.php that returns the headers array (User-Agent and Accept and optionally Authorization), then replace both manual $retryHeaders definitions and any direct header arrays (including the use around $ch2 and the other occurrence at lines ~948-951) with calls to that helper so all curl requests use the same canonical header builder.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
composer.lockis excluded by!**/*.lock
📒 Files selected for processing (110)
.htaccessREADME.mdapp/Controllers/FrontendController.phpapp/Controllers/LibriController.phpapp/Controllers/UpdateController.phpapp/Models/BookRepository.phpapp/Routes/web.phpapp/Support/I18n.phpapp/Support/Updater.phpapp/Views/admin/updates.phpapp/Views/autori/index.phpapp/Views/editori/index.phpapp/Views/events/index.phpapp/Views/frontend/event-detail.phpapp/Views/frontend/events.phpapp/Views/frontend/home-sections/events.phpapp/Views/frontend/home-sections/genre_carousel.phpapp/Views/frontend/home.phpapp/Views/frontend/layout.phpapp/Views/layout.phpapp/Views/libri/index.phpapp/Views/libri/partials/book_form.phpapp/Views/libri/scheda_libro.phpapp/Views/partials/scroll-to-top.phpapp/Views/prestiti/index.phpapp/Views/utenti/index.phpfrontend/js/vendor.jsinstaller/classes/Installer.phpinstaller/database/data_de_DE.sqlinstaller/database/data_en_US.sqlinstaller/database/data_it_IT.sqlinstaller/index.phpinstaller/steps/step0.phplocale/de_DE.jsonlocale/routes_de_DE.jsonpublic/assets/main.csspublic/assets/vendor.bundle.jsscripts/.user.iniscripts/create-release.shscripts/manual-update.phpscripts/manual-upgrade.phptests/full-test.spec.jstests/genre-bugs.spec.jsvendor/composer/autoload_classmap.phpvendor/composer/autoload_files.phpvendor/composer/autoload_static.phpvendor/composer/installed.jsonvendor/composer/installed.phpvendor/graham-campbell/result-type/composer.jsonvendor/laravel/serializable-closure/composer.jsonvendor/laravel/serializable-closure/src/Serializers/Native.phpvendor/laravel/serializable-closure/src/Support/ReflectionClosure.phpvendor/monolog/monolog/README.mdvendor/monolog/monolog/composer.jsonvendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.phpvendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.phpvendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.phpvendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.phpvendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.phpvendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.phpvendor/monolog/monolog/src/Monolog/Handler/CubeHandler.phpvendor/monolog/monolog/src/Monolog/Handler/Curl/Util.phpvendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.phpvendor/monolog/monolog/src/Monolog/Handler/Handler.phpvendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.phpvendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.phpvendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.phpvendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.phpvendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.phpvendor/monolog/monolog/src/Monolog/Handler/StreamHandler.phpvendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.phpvendor/monolog/monolog/src/Monolog/Handler/TestHandler.phpvendor/slim/csrf/composer.jsonvendor/slim/psr7/composer.jsonvendor/slim/psr7/src/Cookies.phpvendor/slim/slim/MAINTAINERS.mdvendor/slim/slim/Slim/App.phpvendor/slim/slim/composer.jsonvendor/slim/slim/psalm.xmlvendor/tecnickcom/tcpdf/CHANGELOG.TXTvendor/tecnickcom/tcpdf/LICENSE.TXTvendor/tecnickcom/tcpdf/README.mdvendor/tecnickcom/tcpdf/VERSIONvendor/tecnickcom/tcpdf/composer.jsonvendor/tecnickcom/tcpdf/config/tcpdf_config.phpvendor/tecnickcom/tcpdf/fonts/ae_fonts_2.0/READMEvendor/tecnickcom/tcpdf/fonts/freefont-20100919/COPYINGvendor/tecnickcom/tcpdf/fonts/freefont-20100919/CREDITSvendor/tecnickcom/tcpdf/fonts/freefont-20120503/COPYINGvendor/tecnickcom/tcpdf/fonts/freefont-20120503/CREDITSvendor/tecnickcom/tcpdf/fonts/freefont-20120503/READMEvendor/tecnickcom/tcpdf/fonts/freefont-20120503/TROUBLESHOOTINGvendor/tecnickcom/tcpdf/include/barcodes/datamatrix.phpvendor/tecnickcom/tcpdf/include/barcodes/pdf417.phpvendor/tecnickcom/tcpdf/include/barcodes/qrcode.phpvendor/tecnickcom/tcpdf/include/tcpdf_colors.phpvendor/tecnickcom/tcpdf/include/tcpdf_filters.phpvendor/tecnickcom/tcpdf/include/tcpdf_font_data.phpvendor/tecnickcom/tcpdf/include/tcpdf_fonts.phpvendor/tecnickcom/tcpdf/include/tcpdf_images.phpvendor/tecnickcom/tcpdf/include/tcpdf_static.phpvendor/tecnickcom/tcpdf/tcpdf.phpvendor/tecnickcom/tcpdf/tcpdf_autoconfig.phpvendor/tecnickcom/tcpdf/tcpdf_barcodes_1d.phpvendor/tecnickcom/tcpdf/tcpdf_barcodes_2d.phpvendor/tecnickcom/tcpdf/tools/tcpdf_addfont.phpvendor/vlucas/phpdotenv/composer.jsonvendor/vlucas/phpdotenv/src/Parser/EntryParser.phpvendor/vlucas/phpdotenv/src/Repository/Adapter/EnvConstAdapter.phpvendor/vlucas/phpdotenv/src/Repository/Adapter/ServerConstAdapter.php
💤 Files with no reviewable changes (7)
- vendor/vlucas/phpdotenv/src/Repository/Adapter/EnvConstAdapter.php
- vendor/vlucas/phpdotenv/src/Parser/EntryParser.php
- vendor/slim/slim/psalm.xml
- vendor/slim/slim/MAINTAINERS.md
- vendor/vlucas/phpdotenv/src/Repository/Adapter/ServerConstAdapter.php
- scripts/manual-update.php
- vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php
| use Monolog\Logger; | ||
| use Psr\Log\LogLevel; | ||
| use Monolog\LogRecord; | ||
| use NoDiscard; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Find root dependency manifests
echo "== composer manifests =="
fd -a '^composer\.json$'
fd -a '^composer\.lock$'
# 2) Print declared PHP constraint(s)
echo
echo "== declared PHP constraints =="
rg -n '"php"\s*:\s*"' $(fd -a '^composer\.json$' 2>/dev/null || true)
# 3) Print locked monolog version(s), if present
echo
echo "== locked monolog versions =="
python - <<'PY'
import json, pathlib
for lock_path in pathlib.Path('.').rglob('composer.lock'):
try:
data = json.loads(lock_path.read_text(encoding='utf-8'))
except Exception:
continue
pkgs = data.get('packages', []) + data.get('packages-dev', [])
for p in pkgs:
if p.get('name') == 'monolog/monolog':
print(f"{lock_path}: monolog/monolog {p.get('version')}")
PY
# 4) Confirm where NoDiscard is used in vendor copy
echo
echo "== NoDiscard usages in monolog vendor file =="
rg -n 'use NoDiscard|#\[NoDiscard\]' vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php 2>/dev/null || echo "File not found or no matches"Repository: fabiodalez-dev/Pinakes
Length of output: 12751
🌐 Web query:
#[NoDiscard] PHP attribute introduction version when released
💡 Result:
#[\NoDiscard](the built-in attribute) was introduced in PHP 8.5, first released in PHP 8.5.0 (Nov 20, 2025). [1][2]- PHP attributes syntax (
#[]in general) were introduced in PHP 8.0, first released in PHP 8.0.0 (Nov 26, 2020). [3][4]
Update minimum PHP version or remove #[NoDiscard] attributes for compatibility.
The #[NoDiscard] attribute was introduced in PHP 8.5 (released Nov 2025), but your project requires ^8.1, which includes PHP 8.1–8.4 where this attribute does not exist. On older PHP versions, the attributes will parse but provide no engine behavior, defeating their purpose of warning when return values are discarded. Either update composer.json to require php: ">=8.5" or remove the #[NoDiscard] annotations from the vendored Monolog code.
Reference: https://www.php.net/manual/en/migration85.new-features.php
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php` at line 18, The
vendored Monolog code uses the PHP 8.5 attribute #[NoDiscard] which is
incompatible with our declared php constraint (^8.1); either raise the minimum
PHP requirement to >=8.5 in composer.json or remove the #[NoDiscard] annotations
from the Monolog handler(s) (e.g., the TestHandler class where #[NoDiscard]
appears) so the code is parseable and behaves consistently on PHP 8.1–8.4; if
choosing removal, delete the #[NoDiscard] attribute tokens (and any related
use/import like NoDiscard) from the relevant classes/methods (e.g., TestHandler)
in the vendor files, or alternatively update composer.json's php version
constraint to ">=8.5" and run composer update to enforce the new requirement.
| * @return string encrypted text | ||
| * @since 2.0.000 (2008-01-02) | ||
| * @author Klemen Vodopivec, Nicola Asuni | ||
| * @author Klemen Vodopivec,2026 Nicola Asuni |
There was a problem hiding this comment.
Fix author docblock punctuation.
Klemen Vodopivec,2026 is missing a space after the comma.
✏️ Suggested fix
- * `@author` Klemen Vodopivec,2026 Nicola Asuni
+ * `@author` Klemen Vodopivec, 2026 Nicola Asuni📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| * @author Klemen Vodopivec,2026 Nicola Asuni | |
| * `@author` Klemen Vodopivec, 2026 Nicola Asuni |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vendor/tecnickcom/tcpdf/include/tcpdf_static.php` at line 495, Fix the author
docblock punctuation by inserting a space after the comma in the author line:
change "Klemen Vodopivec,2026 Nicola Asuni" to "Klemen Vodopivec, 2026 Nicola
Asuni" in the tcpdf_static.php author docblock (search for the "author Klemen
Vodopivec,2026 Nicola Asuni" string to locate the exact line).
| ## Developer(s) Contact | ||
|
|
||
| * Nicola Asuni <info@tecnick.com> | ||
| *2026 Nicola Asuni <info@tecnick.com> |
There was a problem hiding this comment.
Fix malformed developer contact entry on Line 83.
The current Markdown entry is malformed and likely unintended.
Proposed fix
-*2026 Nicola Asuni <info@tecnick.com>
+* Nicola Asuni <info@tecnick.com>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| *2026 Nicola Asuni <info@tecnick.com> | |
| * Nicola Asuni <info@tecnick.com> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vendor/tecnickcom/tcpdf/README.md` at line 83, The README entry "*2026 Nicola
Asuni <info@tecnick.com>" is malformed; update the line in README.md (the
developer contact entry) to be a properly formatted Markdown list/item by adding
the missing space and punctuation — for example change "*2026 Nicola Asuni
<info@tecnick.com>" to "* 2026 Nicola Asuni <info@tecnick.com>" or to a clearer
form like "2026 — Nicola Asuni <info@tecnick.com>" so the contact renders
correctly.
- events.php: use route_path('events') instead of hardcoded /events/ path
- layout.php: reset gPrefixActive on Escape, add aria-label to icon-only buttons
- BookRepository: validate anno_pubblicazione in updateOptionals with filter_var
- Updater: validate retry HTTP status before accepting fallback download content
- scroll-to-top: respect prefers-reduced-motion for smooth scrolling
- Installer: handle unbracketed IPv6 host literals
- manual-upgrade: cast CSRF token to string, add ZIP uncompressed size check
- tests: fix merge selector to target submit button, not form element
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
…#81) - Add do_action('assets.head') and do_action('assets.footer') hooks to frontend layout so plugins can inject CSS/JS on public pages - Replace admin target="_blank" audio link with inline toggle player - Fix GAP stale DOM reference: re-query audio element after GAP.init() which replaces innerHTML and destroys the original node - Add global keyboard shortcuts (←→ seek, ↑↓ volume, Space play/pause) active when player is visible, regardless of focus - Switch audio player theme from green to black & white - Bump digital-library plugin to v1.2.0 - Add version sync for bundled plugins in PluginManager so disk version updates propagate to DB automatically on boot
- Add appReady flag set by Phase 1 on completion or when app already installed - Add beforeEach skip guard to Phase 2-20: skips all tests if Phase 1 failed - Add beforeAll guard to Phase 2-19: prevents browser context creation - Make afterAll close() calls safe with optional chaining - Fix installer radio selector: value="it" → value="it_IT"
- Test GAP CSS/JS assets load on frontend book detail - Test player toggle button and container render - Test admin inline audio player (no target="_blank")
Summary
This PR bundles 4 improvements that address long-standing issues and community requests:
1. Native German language support (de_DE) — Closes discussion #65
locale/de_DE.json(formal "Sie" style, library-specific terminology)/katalog,/anmelden,/buch,/autor,/wunschliste, etc.)data_de_DE.sql): 181 genres, 18 email templates, CMS pages (Über uns, Datenschutzerklärung, Impressum), cookie banner, home content__()function made language-agnostic (loads any locale JSON, not just en_US)2. Dynamic locale registration from DB
$supportedLocalesinweb.phpnow reads fromI18n::getAvailableLocales()instead of hardcoded['it_IT', 'en_US']3. GitHub API token support for Updater
system_settings(category:updater, key:github_token)getGitHubHeaders()POST /admin/updates/tokenwith CSRF validation4. Standalone manual upgrade script
scripts/manual-upgrade.php: password-protected, standalone PHP script for users stuck on old versions that can't use the built-in updater.env, uploads, plugins, covers, configs5. Changelog rewrite
Changed files
locale/de_DE.jsonlocale/routes_de_DE.jsoninstaller/database/data_de_DE.sqlscripts/manual-upgrade.phpapp/Routes/web.phpapp/Support/I18n.phpde_DEto fallback localesapp/Support/Updater.phpapp/Controllers/UpdateController.phpsaveToken()endpointapp/Views/admin/updates.phpinstaller/index.php__(), HTML lang mappinginstaller/steps/step0.phpinstaller/classes/Installer.phpde/de_DElocale mappingREADME.mdTest plan
/katalog), login (/anmelden), admin dashboard@coderabbitai full review
Summary by CodeRabbit
New Features
Improvements
Bug Fixes