Skip to content
Merged
Show file tree
Hide file tree
Changes from 65 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
acb9e85
Restyle the public site and the account pages (2026 design)
fabiodalez-dev Oct 6, 2026
7fbfa30
Match the book page to the design; hero and card style as theme options
fabiodalez-dev Oct 7, 2026
4f8946d
Make the catalogue list view a real list
fabiodalez-dev Oct 7, 2026
2d41036
Fix the public site on phones: hero order, folded filters, profile fi…
fabiodalez-dev Oct 7, 2026
f8aa632
Let the hero books' shadow fall past the fan when it is stacked
fabiodalez-dev Oct 7, 2026
7540e43
Wanted books with real covers, genre rows aligned, archive year field…
fabiodalez-dev Oct 7, 2026
59a2049
Show the book's genre path as the first row of its details
fabiodalez-dev Oct 7, 2026
bab76e8
Give every public form field a soft fill and a thin rule tinted by th…
fabiodalez-dev Oct 7, 2026
c599873
Bring the wishlist page into the 2026 design
fabiodalez-dev Oct 7, 2026
6b3edf3
Lay the footer out on a grid and fix its Seguici column
fabiodalez-dev Oct 7, 2026
29242ee
List the footer's social profiles in four rows, level with Menu and A…
fabiodalez-dev Oct 7, 2026
9e6b8fd
Send readers to the dashboard in the install's language
fabiodalez-dev Oct 7, 2026
1dad80b
Bring the loans and reservations cards into the 2026 design
fabiodalez-dev Oct 7, 2026
c747259
Merge main (0.7.94) into the 2026 restyle
fabiodalez-dev Oct 7, 2026
211add6
Keep the catalogue list a list after paging
fabiodalez-dev Oct 7, 2026
e40d34f
Rebuild main.css from the current views
fabiodalez-dev Oct 7, 2026
b5c8938
Make theme-coloured text readable on every theme
fabiodalez-dev Oct 7, 2026
b736319
Apply the custom CSS, custom scripts and cookie banner on every publi…
fabiodalez-dev Oct 7, 2026
f86575f
Let the home's events section title be edited
fabiodalez-dev Oct 7, 2026
53d6e5f
Raise the admin counters and card notes to AA contrast
fabiodalez-dev Oct 7, 2026
0f55a26
Let the admin take Emeroteca and Archive out of the public menu
fabiodalez-dev Oct 7, 2026
2a7b67e
Check the theme CSS sanitisation in its shared partial
fabiodalez-dev Oct 7, 2026
37d233c
Bring two browser specs in line with the 2026 footer and the CI database
fabiodalez-dev Oct 7, 2026
2069d62
Keep site scripts off the auth pages and retire the dead restyle left…
fabiodalez-dev Oct 7, 2026
76bfbd3
Make the 2026 design readable and operable on every theme
fabiodalez-dev Oct 7, 2026
ad33b4e
Close the remaining review findings: escaping, media types, hero, cat…
fabiodalez-dev Oct 7, 2026
ffc398e
Make the hero covers spec independent of the catalogue it finds
fabiodalez-dev Oct 7, 2026
24500a4
Merge branch 'pr-460' into verify-460
fabiodalez-dev Oct 7, 2026
9f0bc64
Set the accent's text shade too when the field test switches theme
fabiodalez-dev Oct 7, 2026
9415eef
Show an article's image in the header search suggestions (#453)
fabiodalez-dev Oct 7, 2026
2113ab6
Use Fraunces as the serif instead of Newsreader
fabiodalez-dev Oct 7, 2026
7b1bd47
Draw form fields white, and one border per search box
fabiodalez-dev Oct 7, 2026
7a58ff5
Keep 404s and routine plugin saves out of the error log
fabiodalez-dev Oct 7, 2026
3e534ba
Run the book details and keywords across the whole width
fabiodalez-dev Oct 8, 2026
6fa337f
Preserve author searches across facet rebuilds and show complete covers
fabiodalez-dev Oct 8, 2026
a3418d0
Fix catalog navigation and staff article PDF access (#461)
fabiodalez-dev Oct 8, 2026
afcf2ca
Close restyle review findings and keep theme hover contrast readable
fabiodalez-dev Oct 8, 2026
a75562e
test: keep article genre regression aligned with open drill-down
fabiodalez-dev Oct 8, 2026
84a023a
Polish missing book covers with coordinated cloth bindings
fabiodalez-dev Oct 8, 2026
45a7b8f
Complete mobile collection APIs and reconcile Uwe catalogue requests
fabiodalez-dev Oct 8, 2026
f23c2b9
Match localized catalogue language facets to analytic ISO codes
fabiodalez-dev Oct 8, 2026
f561684
Keep article detail independent of mobile middleware loading and tran…
fabiodalez-dev Oct 8, 2026
5fbe76e
Record final Uwe parity validation
fabiodalez-dev Oct 8, 2026
f1a68e8
Preserve release review evidence for mobile and Android parity
fabiodalez-dev Oct 8, 2026
a14ac7b
Exclude worktree Git metadata from release ZIPs and verify real packa…
fabiodalez-dev Oct 8, 2026
14559e1
Tidy the 2026 design and close the review findings
fabiodalez-dev Oct 8, 2026
2e6078e
Line up the catalogue rows and the book actions on phones
fabiodalez-dev Oct 8, 2026
a07b028
Phone catalogue rows: cover level with the title
fabiodalez-dev Oct 8, 2026
a42b8c6
Drop the hero photo remnants and the plugins' stale version caps
fabiodalez-dev Oct 8, 2026
1b7b787
Warm black for the stock theme, lighter archive search, deep Dewey nu…
fabiodalez-dev Oct 9, 2026
d398449
Show the image uploaded for a CMS page
fabiodalez-dev Oct 9, 2026
abbe641
Encrypt push messages and harden the mobile API
fabiodalez-dev Oct 9, 2026
d00e87b
Archives: MARC 21 export, publication flag, EAD3 fixes; FRBR expressi…
fabiodalez-dev Oct 9, 2026
af4b83f
Interoperability: align OAI-PMH, NCIP, SRU, ResourceSync, BIBFRAME, O…
fabiodalez-dev Oct 9, 2026
1c08de6
Periodicals and book clubs: localized addresses, private clubs, invit…
fabiodalez-dev Oct 9, 2026
5460d2d
Catalogue sources and media plugins: safer keys, honest defaults, sha…
fabiodalez-dev Oct 9, 2026
919da80
Quick search: one list of records, best title match first, then alpha…
fabiodalez-dev Oct 9, 2026
4594f0b
Translate the new plugin messages; pass waitForFunction timeouts corr…
fabiodalez-dev Oct 9, 2026
eeece36
Close the review findings on unpublished archives, PDF ranges and the…
fabiodalez-dev Oct 9, 2026
bdd63c5
Make the CI suites match the new behaviour and the runner
fabiodalez-dev Oct 9, 2026
ed28e2c
Do not read non-HTML responses into memory in the security-header mid…
fabiodalez-dev Oct 9, 2026
5e8455d
Fix the desiderata ResourceSync check for the new change-list signatu…
fabiodalez-dev Oct 9, 2026
ec6994a
Run the mirrored integration suites in strict mode, as CI does
fabiodalez-dev Oct 9, 2026
f5ed1ab
Browser specs: tolerate the doubled nosniff header and skip inactive …
fabiodalez-dev Oct 9, 2026
710a970
Book covers fill the whole book again
fabiodalez-dev Oct 9, 2026
483f195
Update the cover comments to the restored fill behaviour
fabiodalez-dev Oct 9, 2026
b11e638
Phone tab bar, as the Android app's bottom navigation
fabiodalez-dev Oct 9, 2026
89612f8
Keep the favourites badge in step with the book page and the favourit…
fabiodalez-dev Oct 9, 2026
6f5ffc7
Mobile book club: a private club's card and Join stay reachable, its …
fabiodalez-dev Oct 9, 2026
9690cee
Release 0.8.0: version, changelog and README highlights
fabiodalez-dev Oct 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .distignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# ========================================

# Version control
.git/
.git
.gitignore
.gitattributes

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-browser-security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ jobs:
done < <(find /etc/php -type d -path '*/conf.d' | sort -u)

- name: Setup PHP 8.2 tooling
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: '8.2'
extensions: mysqli, pdo_mysql, mbstring, curl, intl, xml, zip, gd, apcu
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-database-compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ jobs:
persist-credentials: false

- name: Setup PHP 8.2
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: '8.2'
extensions: mysqli, pdo_mysql, mbstring, curl, intl, xml
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci-deep-regression.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ jobs:
done < <(find /etc/php -type d -path '*/conf.d' | sort -u)

- name: Setup PHP 8.2
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: '8.2'
extensions: mysqli, mbstring, json, curl, openssl, zip, gd, intl, xml, apcu
Expand Down Expand Up @@ -300,7 +300,7 @@ jobs:
[ -z "${php_module}" ] || sudo a2enmod "${php_module}"
sudo a2dissite 000-default || true
- name: Setup PHP and Node
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: '8.2'
extensions: mysqli, mbstring, curl, zip, gd, intl, xml
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ jobs:

# ── PHP 8.2 CLI for Composer and tooling ────────────────────────────────
- name: Setup PHP 8.2
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: '8.2'
extensions: mysqli, mbstring, json, curl, openssl, zip, gd, intl, xml, apcu
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/ci-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ jobs:
persist-credentials: false

- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: '8.2'
extensions: mysqli, pdo_mysql
Expand Down Expand Up @@ -351,6 +351,7 @@ jobs:
env:
CI_STRICT_TESTS: '1'
run: |
php tests/mobile-collections.integration.php
php tests/desiderata.integration.php
php tests/desiderata-visibility.integration.php
php tests/desiderata-extended.integration.php
Expand Down Expand Up @@ -385,7 +386,7 @@ jobs:
with:
persist-credentials: false
- name: Setup PHP ${{ matrix.php }}
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: ${{ matrix.php }}
extensions: mysqli, pdo_mysql, mbstring, curl, intl, xml
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-real-upgrade.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ jobs:
done < <(find /etc/php -type d -path '*/conf.d' | sort -u)

- name: Setup PHP 8.2
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: '8.2'
extensions: mysqli, mbstring, json, curl, openssl, zip, gd, intl, xml, apcu
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/ci-security-supply-chain.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ jobs:
persist-credentials: false

- name: Setup PHP 8.2
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: '8.2'
extensions: mysqli, pdo_mysql, mbstring, curl, intl, xml, zip
Expand All @@ -146,6 +146,9 @@ jobs:
- name: Verify generated assets match the commit
run: git diff --exit-code -- public/assets

- name: Verify packaging from a Git worktree
run: bash tests/release-worktree.test.sh

- name: Build the release twice and require byte-for-byte reproducibility
run: |
bash bin/build-release.sh --skip-build
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-upgrade-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ jobs:
persist-credentials: false

- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: '8.2'
extensions: mysqli, curl, zip, mbstring
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
run: bash scripts/ci-verify-release-source.sh

- name: Setup PHP 8.2
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: '8.2'
extensions: mysqli, pdo_mysql, mbstring, curl, intl, xml, zip, gd
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test-migrations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
persist-credentials: false

- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: '8.2'
coverage: none
Expand Down Expand Up @@ -84,7 +84,7 @@ jobs:
persist-credentials: false

- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: '8.2'
extensions: mysqli
Expand Down Expand Up @@ -218,7 +218,7 @@ jobs:
persist-credentials: false

- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: '8.2'
extensions: mysqli, pdo_mysql
Expand Down
2 changes: 1 addition & 1 deletion .rsync-filter
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
# ========================================

# Version control
- .git/
- .git
- .gitignore
- .gitattributes
- .gitmodules
Expand Down
104 changes: 67 additions & 37 deletions app/Controllers/Admin/CmsAdminController.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,16 @@ public function editPage(Request $request, Response $response, array $args): Res
$correctSlug = CmsHelper::getRedirectSlug($slug, $currentLocale);
if ($correctSlug !== null) {
// Redirect 301 to correct localized admin slug
// Keep the query (?saved=1, ?error=…): it carries the save outcome.
$query = $request->getUri()->getQuery();
return $response
->withHeader('Location', '/admin/cms/' . $correctSlug)
->withHeader('Location', url('/admin/cms/' . $correctSlug) . ($query !== '' ? '?' . $query : ''))
->withStatus(301);
}

// Recupera la pagina dal database (slug + locale)
$stmt = $this->db->prepare("
SELECT id, slug, locale, title, content, image, meta_description, is_active
FROM cms_pages
WHERE slug = ? AND locale = ?
");
$stmt->bind_param('ss', $slug, $currentLocale);
$stmt->execute();
$result = $stmt->get_result();
$page = $result->fetch_assoc();
$stmt->close();
// The page under this slug, or under another slug of the same page
// (an it_IT install seeded with 'about-us'): never a duplicate.
$page = $this->findPageRow($slug, $currentLocale);

if (!$page) {
// Check if this is a known CMS page that should exist
Expand Down Expand Up @@ -101,6 +95,9 @@ public function editPage(Request $request, Response $response, array $args): Res
}
}

// An image uploaded by an older version may sit outside the web root.
\App\Support\CmsImageStorage::ensurePublic($page['image'] ?? null);

// Passa i dati alla view
$pageData = $page;
$title = sprintf(__('Modifica %s'), $page['title']);
Expand Down Expand Up @@ -131,26 +128,57 @@ public function updatePage(Request $request, Response $response, array $args): R
$metaDescription = $data['meta_description'] ?? '';
$isActive = isset($data['is_active']) ? 1 : 0;

// Aggiorna la pagina (slug + locale)
// The row the editor showed: the same lookup as editPage(), so a page
// stored under another slug of the same page is the one updated.
$page = $this->findPageRow($slug, $currentLocale);
if ($page === null) {
return $response
->withHeader('Location', url('/admin/cms/' . $slug . '?error=db'))
->withStatus(302);
}
$pageRowId = (int) $page['id'];

$stmt = $this->db->prepare("
UPDATE cms_pages
SET title = ?, content = ?, image = ?, meta_description = ?, is_active = ?, updated_at = NOW()
WHERE slug = ? AND locale = ?
WHERE id = ?
");
$stmt->bind_param('ssssiss', $title, $content, $image, $metaDescription, $isActive, $slug, $currentLocale);
$stmt->bind_param('ssssii', $title, $content, $image, $metaDescription, $isActive, $pageRowId);

if ($stmt->execute()) {
$stmt->close();
return $response
->withHeader('Location', '/admin/cms/' . $slug . '?saved=1')
->withStatus(302);
} else {
$error = $this->db->error;
$stmt->close();
return $response
->withHeader('Location', '/admin/cms/' . $slug . '?error=db')
->withStatus(302);
}
$ok = $stmt->execute();
$stmt->close();
// Back to the editor under the locale's own slug (no extra 301 hop).
return $response
->withHeader('Location', url('/admin/cms/' . CmsHelper::getLocalizedSlug($slug, $currentLocale) . ($ok ? '?saved=1' : '?error=db')))
->withStatus(302);
}

/**
* The cms_pages row for a slug in a locale: the exact slug first, then any
* other slug of the same page (CmsHelper's map), as the public page does.
*
* @return array<string, mixed>|null
*/
private function findPageRow(string $slug, string $locale): ?array
{
$pageId = CmsHelper::getPageIdFromSlug($slug);
$variants = $pageId !== null ? CmsHelper::getSlugsForPage($pageId) : [];
$slugs = array_values(array_unique(array_merge([$slug], $variants)));
$placeholders = implode(',', array_fill(0, count($slugs), '?'));
$stmt = $this->db->prepare("
SELECT id, slug, locale, title, content, image, meta_description, is_active
FROM cms_pages
WHERE slug IN ($placeholders) AND locale = ?
ORDER BY slug = ? DESC, id ASC
LIMIT 1
");
$params = array_merge($slugs, [$locale, $slug]);
$stmt->bind_param(str_repeat('s', count($params)), ...$params);
$stmt->execute();
$result = $stmt->get_result();
$row = $result instanceof \mysqli_result ? $result->fetch_assoc() : null;
$stmt->close();
return is_array($row) ? $row : null;
}

public function uploadImage(Request $request, Response $response): Response
Expand Down Expand Up @@ -201,20 +229,22 @@ public function uploadImage(Request $request, Response $response): Response
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
}

// SECURITY: Store uploads outside web directory to prevent direct access
$baseDir = realpath(__DIR__ . '/../../../storage/uploads');
if ($baseDir === false) {
error_log("Upload base directory not found");
// The image is shown on a public page, so it is stored where its URL
// (/uploads/cms/<file>) is served from: public/uploads/cms. It used to
// be written to storage/uploads/cms, outside the web root, while the
// same /uploads/cms URL was saved: every CMS image answered 404.
$uploadPath = \App\Support\CmsImageStorage::publicDir();
if (!is_dir($uploadPath) && !@mkdir($uploadPath, 0755, true) && !is_dir($uploadPath)) {
\App\Support\SecureLogger::error('[CMS] upload directory cannot be created: ' . $uploadPath);
$payload = json_encode(['error' => __('Errore di configurazione del server.')] );
$response->getBody()->write($payload);
return $response->withHeader('Content-Type', 'application/json')->withStatus(500);
}

$uploadPath = $baseDir . '/cms';

// Crea directory se non esiste
if (!is_dir($uploadPath)) {
mkdir($uploadPath, 0755, true);
$baseDir = realpath($uploadPath);
if ($baseDir === false) {
$payload = json_encode(['error' => __('Errore di configurazione del server.')] );
$response->getBody()->write($payload);
return $response->withHeader('Content-Type', 'application/json')->withStatus(500);
}

// SECURITY: Generate cryptographically secure random filename
Expand Down
5 changes: 4 additions & 1 deletion app/Controllers/AuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,10 @@ public function login(Request $request, Response $response, mysqli $db): Respons
} elseif (in_array($row['tipo_utente'], ['admin', 'staff'], true)) {
$redirectUrl = '/admin/dashboard';
} else {
$redirectUrl = '/user/dashboard';
// The dashboard in the reader's language, set just above from their
// profile (/utente/bacheca for an Italian reader), not the English
// path, which also answers but is not canonical there.
$redirectUrl = RouteTranslator::route('user_dashboard');
}

return $response->withHeader('Location', $redirectUrl)->withStatus(302);
Expand Down
10 changes: 9 additions & 1 deletion app/Controllers/AutoriController.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ public function store(Request $request, Response $response, mysqli $db): Respons
$collegamenti = $this->buildCollegamentiJson($data);

try {
$repo->create([
$newAuthorId = $repo->create([
'nome' => trim($data['nome'] ?? ''),
'pseudonimo' => trim($data['pseudonimo'] ?? ''),
'data_nascita' => $data['data_nascita'] ?? null,
Expand All @@ -154,6 +154,14 @@ public function store(Request $request, Response $response, mysqli $db): Respons
if ($photo['deleteOnSuccess'] !== null) {
$this->deleteLocalPhoto($photo['deleteOnSuccess']);
}
// Plugins that add fields to the create form (author.create.fields)
// store them now that the row exists. A plugin failure never undoes
// the author the librarian just created.
try {
\App\Support\Hooks::do('author.created', [$newAuthorId, $data]);
} catch (\Throwable $e) {
\App\Support\SecureLogger::error('author.created listener failed: ' . $e->getMessage());
}
return $response->withHeader('Location', url('/admin/authors'))->withStatus(302);
}

Expand Down
Loading
Loading