Repository navigation
Releases: LordOfTheSnow/phpcirclebook
Releases · LordOfTheSnow/phpcirclebook
Release list
v1.1.2
Added
- Outgoing emails (recipient list, admin approval request, approval confirmation) now end
with a translatable footer naming the app and itsAPP_URL, e.g. "Originated by My
Mailing List (https://list.example.com)" (mail.footerinlang/). - Optional
TRUST_PROXYsetting (.env): when the app runs behind a single trusted
reverse proxy or load balancer, the client IP used for rate limiting is read from the
X-Forwarded-Forheader instead of the proxy's own address. Leftfalseby default.
Fixed
- Mail subjects and the
Fromdisplay name are now RFC 2047 MIME-encoded
(Mailer::encodeHeaderValue()) instead of sent as raw UTF-8. Some receiving mail
servers rejected non-ASCII subjects outright ("550 Subject contains invalid
characters"), even though most tolerate raw UTF-8 headers. - The web admin tool now shows a warning (instead of failing silently) when a status
change to "approved" triggers a confirmation email that can't be sent, matching the
CLI tool's existing behaviour.
Security
- Fixed a CSV/spreadsheet formula injection vector: the
name,public_note, andtags
fields in the mailed recipient-list CSV attachment are now escaped (a leading=,+,
-,@, tab, or carriage return is prefixed with'), so a registrant-supplied value
can no longer be interpreted as a formula (e.g.=HYPERLINK(...)) by Excel or Sheets. - Pico CSS is now loaded from the CDN pinned to an exact version with a Subresource
Integrity hash, instead of a floating@2tag with no integrity check. - The admin tool now sends
X-Frame-Options: DENYand aframe-ancestors 'none'CSP
directive, preventing the login page from being framed for clickjacking / UI-redressing
attacks.
v1.1.1
Changed
- Admin activity log moved from the bottom of the recipients page into a modal overlay.
A "Logs" button in the action bar (next to "Add recipient" and "Log out") opens the
overlay; it can be closed with the × button, a click on the backdrop, or the Escape
key. The overlay is 85 % of the viewport width and scrolls vertically when the log
contains more entries than fit on screen, with the column header remaining sticky. - Unsubscribe events are now recorded in the activity log (
unsubscribedevent) when a
recipient removes themselves via the unsubscribe link.
v1.1.0
Added
- Activity log in the admin tool: a bounded log of the 200 most recent events, shown at
the bottom of the admin page in a fixed-height, scrollable table (newest first). Logged
events are membership applications (who applied), the admin's approve/reject decision,
recipient deletions, and list requests by already-approved members. Entries are stored
in a newlogstable and pruned to the newest 200 on each insert. - List stats on the main page: below the info card, the current number of entries and the
date of the last update are shown as a localised line (e.g. "No. of entries: 8. Last
update: 30.08.2026."). Hidden while the list is empty. APP_TIMEZONEsetting: timezone used to display stored timestamps (the admin activity
log and the main-page "last update" date). Any PHP timezone identifier, e.g.
Europe/Berlin. Timestamps are stored in UTC and converted for display; when unset or
invalid, the server's default timezone is used.
Changed
- Recipient list emails now list public notes in a separate section below the addresses,
under a divider, instead of appending them inline after each address. This keeps the
address block clean so it can be copied straight into an email client. Each note line
has the formName (email): note, and only recipients that have a public note appear.
The CSV attachment is unchanged and still carries the note column. formatDate()now honours the timezone carried by aDateTimeInterfaceargument
(previously theintlformatter ignored it and used PHP's default timezone), so
timezone-converted values format on the correct local calendar day. Bare Unix-timestamp
callers are unaffected.
v1.0.0
Added
- Optional footer line on the main form, configurable via
APP_FOOTERin.env.
Shown only when set, with its own smaller serif styling and a divider above it. - Optional sidebar on the main form with two Markdown-driven cards, "Upcoming events"
(content/events.md) and "Links" (content/links.md). Each card appears only when
its file exists and is non-empty; if neither is present, the form stays full-width.
The side is configurable viaSIDEBAR_SIDE(left/right, defaultright) and the
layout stacks below the form on narrow screens. Content is rendered with
league/commonmarkin a hardened mode (raw HTML stripped, unsafe links disallowed).
Links in the cards open in a new tab (target="_blank",rel="noopener noreferrer").
See ADR-004. - Logo left of the app name in the header, configurable via
APP_LOGOin.env(a
filename served frompublic/, or an absolutehttpsURL). An empty value shows no
logo; the shipped.env.exampledefaults tofavicon.svg, so fresh installs show the
bundled favicon. The logo auto-scales to the header height and may exceed it by at most
40px; when taller than the title, the header row grows and its contents stay vertically
centred. - Password show/hide toggle on the admin login field (an eye icon at the right edge).
- Header attribution restyled: a smaller "powered by PHPCircleBook v… [GitHub icon]" link
that wraps cleanly on mobile, on both the main and admin pages.
Security
- Fixed an email header injection vector: a registrant's name flowed into the approval
email subject, and a name containing CR/LF could inject extra mail headers (e.g.Bcc)
or body content. Mail subjects are now stripped of all CR/LF at the lowest level in
Mailer(sanitizeHeaderValue), covering all callers. Recipient addresses were already
validated withFILTER_VALIDATE_EMAIL. - Hardened the admin
statusaction to acceptPOSTonly (GET now redirects back to the
list), matching the other state-changing actions. It was already CSRF-protected; this
removes the inconsistency.
v0.9.0
First tagged release. Covers the complete mailing-list application built so far: an
admin-gated registration flow, self-service list retrieval, and the supporting CLI
tooling, all running on plain PHP hosting with a single-file SQLite database.
Added
- Admin-gated registration: visitors submit their email (and optional name) and the
administrator receives an approval request with one-click approve/reject links. Nothing
is exposed to the applicant until an admin acts. - Optional comment field on the registration form: a free-text field (max 500 characters,
enforced both client-side and server-side) shown beneath the name field. The comment is
stored in the database and included in the approval-request email to the admin, but is
deliberately excluded from the recipient list and its CSV export. - Self-service list retrieval: approved members can request the current contact list at any
time, delivered by email with a CSV attachment of all members. - Unsubscribe flow with a confirmation page, protected by an HMAC-signed token so links
can't be forged. - Bot and abuse protection: a hidden honeypot field plus per-IP and per-email rate limiting.
- Internationalisation: all user-facing strings live in per-locale PHP files (
lang/en.php,
lang/de.php), selected viaAPP_LOCALE, with English as the fallback for missing keys.
Dates and numbers are formatted through theintlextension per locale. - Configurable info card on the main page via
APP_DESCRIPTION, and an obfuscated admin
contact email to deter harvesters. - CLI import tool (
bin/import.php) for bulk-adding existing members from a
semicolon-separated file. Entries are added as approved; the summary now reports which
lines were actually imported alongside skipped duplicates and invalid lines. - CLI rate-limit reset tool (
bin/reset-ratelimit.php) to clear throttling during testing. - MIT
LICENSEfile and project metadata (license,authors) incomposer.json. - README badges (license, PHP version, SQLite, no-build) and setup/usage documentation.
Changed
- Registration form styling made more prominent, especially on mobile: bolder, slightly
larger field labels and clearly bordered, subtly filled input fields with a visible focus
ring. Implemented by retuning Pico CSS's own custom properties and matching its control
selectors, rather than plain overrides that Pico's shorthand would reset.