Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,28 @@

Full version-by-version history for Pinakes. The README shows only the latest release; everything older lives here.

## [Unreleased]
## [0.7.89]

### Added
- **Multiple digital attachments on one book** ([#445](https://github.com/fabiodalez-dev/Pinakes/issues/445)): PDF/ePub editions, reviews and articles about the book, and multiple audio recordings, each with a title, download and appropriate viewer. Uploads append files; existing single-file records remain available. Digital Library is now 1.4.0.
- **Oxford (Umeå) in the shared Cite dialog** for books, articles and anthology chapters, alongside APA, Harvard, MLA and Chicago ([#412](https://github.com/fabiodalez-dev/Pinakes/issues/412)).
- **A book records where it was published** ([#412](https://github.com/fabiodalez-dev/Pinakes/issues/412)). The book form has a *Place of publication* field, shown on the public and admin record. Chicago and Harvard citations now read "Lund: Nordic Academic Press", the MARCXML export carries it in 264 `$a` (SRU and OAI-PMH), and an empty place leaves no dangling colon. Core migration 0.7.89 adds the nullable `libri.luogo_pubblicazione` column; existing books get no invented value.
- **A book's citation can be downloaded as RIS** for EndNote, Zotero and Mendeley, from the Cite dialog, built from the same data as the citation styles.
- **Oxford (Umeå) in the shared Cite dialog** for books, articles and anthology chapters, alongside APA, Harvard, MLA and Chicago ([#412](https://github.com/fabiodalez-dev/Pinakes/issues/412)).
- **Articles in the live search and on the catalogue** ([#412](https://github.com/fabiodalez-dev/Pinakes/issues/412)). The header search suggests published articles while the reader types; on /catalogo an article card links its authors and its publication. The article form links a record to a catalogued masthead by searching it and copies the masthead's title and ISSN into empty fields. Article MARCXML gains the MARC country code in 008/15-17, the public PDF in 856 and the authors in direct order in 245 `$c`; RIS names the journal in JF as well.
- **Multiple digital attachments on one book** ([#445](https://github.com/fabiodalez-dev/Pinakes/issues/445)): PDF/ePub editions, reviews and articles about the book, and multiple audio recordings, each with a title, download and appropriate viewer. Uploads append files; existing single-file records remain available. Digital Library is now 1.4.0.
- **The catalogue header is editable per language.** Settings → CMS has a Catalogue card with a title and subtitle for every active language; an empty field keeps the shipped wording.
- **"New article" in the sidebar quick actions**, under "New book", whenever the Emeroteca is active ([#412](https://github.com/fabiodalez-dev/Pinakes/issues/412)).

### Changed
- **The public site reads as one product.** The periodicals section is rebuilt and linked end to end (masthead, year, issue, article, with previous/next and a way back), and every public page uses the same shared pieces: catalogue hero, breadcrumb, filter sidebar, article card, pagination and resource hero.
- **Event images: three presets instead of four.** "Thumbnail beside the text" rendered exactly like the recommended cover, so it was merged into it; an event saved with it looks the same. The cover beside the title is now held to 350px (a masthead logo to 240px), as intended: a global `max-width: 100% !important` on images had been letting it fill its column.

### Fixed
- **An update that cannot fit is refused before anything is touched.** The updater measures the space the rollback copy needs and proves the account can write before starting, and a copy error now names its cause (space or quota, permissions, missing directory) instead of an arbitrary file name. The standalone recovery script does the same, and its "space checked by hand" option still writes 16 MB first, so a full disk stops there rather than halfway through the dump.
- **Saving an article never unlinks its masthead by accident.** A masthead that sorted past the picker's list, or a list that could not be read, left the form on "not linked" and a plain save removed the link and the issue.
- **A blank place of publication is stored as empty**, not as the spaces typed into it.
- **The events pages keep their own styles.** The listing's spacing and the event image's margin and rounded corners were silently dropped, because the page wrapped its rules in a second `<style>` tag inside the layout's.
- **Digital files linked before 0.7.89 stay visible after the upgrade.** A book whose eBook or audiobook pointed to a path of the site outside `/uploads/` showed it before; the new attachment list now accepts any path of the same site (never `//`, `..` or a backslash), so such a book neither loses its file on the public page nor refuses to save.

### Verified for the release candidate
- All of Uwe's cataloguing requests in #412 are mapped to implementation and regression coverage in `docs/reviews/issue-412-verifica.md`. This candidate includes the prerequisite release branches #439, #440 and #441.

## [0.7.88]

Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,17 @@ Pinakes is a self-hosted, full-featured ILS for schools, municipalities, and pri

Highlights of the latest release are below. The full version-by-version history (v0.7.59 → v0.6.x) lives in **[CHANGELOG.md](CHANGELOG.md)**.

### v0.7.88 — latest
### v0.7.89 — latest

**A book now records where it was published** ([#412](https://github.com/fabiodalez-dev/Pinakes/issues/412)). The book form has a *Place of publication* field (core migration `migrate_0.7.89.sql`, a nullable column: existing books get no invented value). It appears on the record, reaches the Chicago and Harvard citations as "Place: Publisher" and the MARCXML export in 264 `$a`, and every book's Cite dialog now offers a RIS file for EndNote, Zotero and Mendeley, built from the same data as the citations. Oxford (Umeå) joins APA, Chicago, MLA and Harvard in that dialog.

**Articles are found the way books are.** The header search suggests published articles while the reader types; on the catalogue an article's authors and publication are links; the article form links a record to its masthead by searching it, and fills the masthead's title and ISSN. The periodicals section is rebuilt and linked end to end, and every public page is assembled from the same shared pieces, so the catalogue, the emeroteca, archives and events read as one site. "New article" sits under "New book" in the sidebar when the Emeroteca is active.

**One book, several digital files** ([#445](https://github.com/fabiodalez-dev/Pinakes/issues/445)). A record can carry more than one edition, related reviews and articles, and several audio tracks, each with its own title and viewer; books linked to a single file before the upgrade keep it. The catalogue's title and subtitle are editable per language from Settings → CMS.

**An update that cannot fit is refused before anything is touched.** The updater measures the space the rollback copy needs and proves the account can write before it starts, and a copy error names its cause (space or quota, permissions, missing directory) instead of an arbitrary file name.

### v0.7.88

**An article you hold a copy of is catalogued as what it is** ([#412](https://github.com/fabiodalez-dev/Pinakes/issues/412)). A photocopy, an offprint or the PDF of one paper out of a journal the library does not take can now carry the apparatus of an analytic record: subtitle, language and country of the host, a classification stored as scheme plus value, a note that the library holds a copy and not the run, and the electronic location with its access conditions. Every one of those fields is optional, so a collection that only wants a citation still fills in a title and stops. "Add article" sits beside "New book" in book management.

Expand Down
8 changes: 4 additions & 4 deletions app/Controllers/FrontendController.php
Original file line number Diff line number Diff line change
Expand Up @@ -713,10 +713,6 @@ static function () use ($db): array {
->withHeader('X-LiteSpeed-Cache-Control', 'no-cache');
}

/**
* Render the public book-detail page, loading the book with its authors,
* publishers (issue #143), series, reviews and related volumes.
*/
/**
* The book as a RIS file (#412), from the same data as the Cite dialog.
* Visibility is the detail page's: a soft-deleted or unknown book is a 404.
Expand Down Expand Up @@ -753,6 +749,10 @@ public function bookCitationRis(Request $request, Response $response, mysqli $db
->withHeader('X-Content-Type-Options', 'nosniff');
}

/**
* Render the public book-detail page, loading the book with its authors,
* publishers (issue #143), series, reviews and related volumes.
*/
public function bookDetail(Request $request, Response $response, mysqli $db): Response
{
$params = $request->getQueryParams();
Expand Down
6 changes: 4 additions & 2 deletions app/Support/DigitalAttachments.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ public static function normalize(mixed $rows): array
$url = trim($row['url']);
if ($url === '') { continue; }
$kind = $row['kind'] ?? 'ebook';
// Accept HTTP resources and installation-relative uploads; never executable schemes,
// Accept HTTP resources and paths on this site (uploads, but also any other
// root-relative path a record held before the list existed, which the old
// single-file field stored and showed as is); never executable schemes,
// protocol-relative URLs, backslashes or traversal paths.
$local = str_starts_with($url, '/uploads/') && !str_contains(rawurldecode($url), '..')
$local = str_starts_with($url, '/') && !str_starts_with($url, '//') && !str_contains(rawurldecode($url), '..')
&& !str_contains(rawurldecode($url), '\\') && !str_contains(rawurldecode($url), '//');
if ((!$local && HtmlHelper::sanitizePublicHttpUrl($url) === '')
|| preg_match('/[\x00-\x20\x7f]/', $url) || strlen($url) > 2048
Expand Down
66 changes: 1 addition & 65 deletions app/Views/frontend/cms-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,71 +6,7 @@
// Intestazione condivisa (catalog-hero.php): ne serve il foglio di stile.
$catalogPageStyles = true;

$additional_css = "
.static-page {
padding: 3rem 0 4rem;
background: var(--white);
}

.static-content {
max-width: 72ch;
margin: 0;
line-height: 1.8;
color: var(--text-color);
font-size: 1.0625rem;
}

.static-content p {
margin-bottom: 1.25rem;
}

.static-content h2,
.static-content h3,
.static-content h4 {
font-family: var(--serif);
color: var(--text-color);
letter-spacing: -0.02em;
font-weight: 460;
margin: 2.5rem 0 1rem;
}

.static-content h2 { font-size: 1.75rem; }
.static-content h3 { font-size: 1.375rem; }

.static-content ul,
.static-content ol {
margin-bottom: 1.25rem;
padding-left: 1.5rem;
}

.static-content li {
margin-bottom: 0.5rem;
}

.static-content a {
color: var(--text-color);
border-bottom: 1px solid var(--border-color);
text-decoration: none;
}

.static-content a:hover {
color: var(--primary-color);
border-color: var(--primary-color);
}

.static-content img {
max-width: 100%;
height: auto;
border-radius: 3px;
}

.static-content blockquote {
border-left: 1px solid var(--primary-color);
padding-left: 1.5rem;
margin: 2rem 0;
color: var(--text-light);
}

$additional_css = (require __DIR__ . '/partials/static-page-css.php') . "
.static-image {
display: block;
width: 100%;
Expand Down
66 changes: 1 addition & 65 deletions app/Views/frontend/cookies-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,71 +8,7 @@
// Intestazione condivisa (catalog-hero.php): ne serve il foglio di stile.
$catalogPageStyles = true;

$additional_css = "
.static-page {
padding: 3rem 0 4rem;
background: var(--white);
}

.static-content {
max-width: 72ch;
margin: 0;
line-height: 1.8;
color: var(--text-color);
font-size: 1.0625rem;
}

.static-content p {
margin-bottom: 1.25rem;
}

.static-content h2,
.static-content h3,
.static-content h4 {
font-family: var(--serif);
color: var(--text-color);
letter-spacing: -0.02em;
font-weight: 460;
margin: 2.5rem 0 1rem;
}

.static-content h2 { font-size: 1.75rem; }
.static-content h3 { font-size: 1.375rem; }

.static-content ul,
.static-content ol {
margin-bottom: 1.25rem;
padding-left: 1.5rem;
}

.static-content li {
margin-bottom: 0.5rem;
}

.static-content a {
color: var(--text-color);
border-bottom: 1px solid var(--border-color);
text-decoration: none;
}

.static-content a:hover {
color: var(--primary-color);
border-color: var(--primary-color);
}

.static-content img {
max-width: 100%;
height: auto;
border-radius: 3px;
}

.static-content blockquote {
border-left: 1px solid var(--primary-color);
padding-left: 1.5rem;
margin: 2rem 0;
color: var(--text-light);
}
";
$additional_css = require __DIR__ . '/partials/static-page-css.php';

ob_start();
?>
Expand Down
2 changes: 0 additions & 2 deletions app/Views/frontend/event-detail.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@

// Only what the shared sheets do not cover: a wide image in the body.
$additional_css = "
<style>
.event-cover { margin: 0 0 2rem; border-radius: 3px; overflow: hidden; }
.event-cover img { display: block; width: 100%; height: auto; }
.event-cover--banner img { height: 220px; object-fit: cover; }
Expand All @@ -118,7 +117,6 @@
.book-card--event .book-image { object-fit: cover; }
.book-card--event .book-image-icon { font-size: 3rem; color: var(--text-muted); opacity: 0.6; }
@media (max-width: 767px) { .event-cover--banner img { height: 160px; } }
</style>
";

ob_start();
Expand Down
2 changes: 0 additions & 2 deletions app/Views/frontend/events.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,11 @@
// Event cards are book cards with a landscape image box (events have posters,
// not 2:3 covers); everything else comes from catalog-pages.css.
$additional_css = "
<style>
.events-listing { padding-top: 2.5rem; padding-bottom: 4rem; }
.books-grid--events { grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr)); }
.book-card--event .book-image-container { aspect-ratio: 4 / 3; display: flex; align-items: center; justify-content: center; }
.book-card--event .book-image { object-fit: cover; }
.book-card--event .book-image-icon { font-size: 3rem; color: var(--text-muted); opacity: 0.6; }
</style>
";

ob_start();
Expand Down
74 changes: 74 additions & 0 deletions app/Views/frontend/partials/static-page-css.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php
/**
* The text-page surface shared by the CMS, privacy and cookie pages: a column
* of readable width with the content's own heading and paragraph rhythm.
* Returned as a string because each page passes it through $additional_css,
* inside the layout's <style>, where it has always been: moving it to
* catalog-pages.css would put it after the layout and change what wins.
* The contact page keeps its own variant.
*/
return <<<'CSS'
.static-page {
padding: 3rem 0 4rem;
background: var(--white);
}

.static-content {
max-width: 72ch;
margin: 0;
line-height: 1.8;
color: var(--text-color);
font-size: 1.0625rem;
}

.static-content p {
margin-bottom: 1.25rem;
}

.static-content h2,
.static-content h3,
.static-content h4 {
font-family: var(--serif);
color: var(--text-color);
letter-spacing: -0.02em;
font-weight: 460;
margin: 2.5rem 0 1rem;
}

.static-content h2 { font-size: 1.75rem; }
.static-content h3 { font-size: 1.375rem; }

.static-content ul,
.static-content ol {
margin-bottom: 1.25rem;
padding-left: 1.5rem;
}

.static-content li {
margin-bottom: 0.5rem;
}

.static-content a {
color: var(--text-color);
border-bottom: 1px solid var(--border-color);
text-decoration: none;
}

.static-content a:hover {
color: var(--primary-color);
border-color: var(--primary-color);
}

.static-content img {
max-width: 100%;
height: auto;
border-radius: 3px;
}

.static-content blockquote {
border-left: 1px solid var(--primary-color);
padding-left: 1.5rem;
margin: 2rem 0;
color: var(--text-light);
}
CSS;
Loading
Loading