Repository navigation
PN Scripts Press 1.1.0
PN Scripts Press 1.1.0 adds content in several languages (English and Bulgarian out of the box), a redirects manager, a page cache, child themes, plugin dependencies, update notices and two-factor sign-in.
Install
composer create-project pnscripts/pn-press my-site
cd my-site && php artisan db:seed && npm install && npm run build && php artisan serveRequires PHP 8.4+. Full steps in the README.
Added
-
Languages (multilingual content):
PRESS_LOCALES=en,bg. Pages, posts, categories and menus have a language; the default language lives at/, the others under/{code}. Per-language slugs and page paths, a Translate action (linked draft copy, parent page and category mapped), a language switcher (press.partials.language-switcher),hreflang+x-defaultlinks,og:locale, per-language site name, tagline, footer text and home page, per-language menus with an "every language" fallback, dates in the page language, Bulgarian front-end strings (lang/bg.json, validation, pagination), Bulgarian demo content. Helperspress_route(),press_locale_url(),App\Press\I18n\Locales. Guide:docs/multilingual.md. -
Redirects manager (Content → Redirects): 301/302/307/308 from old addresses with hit counts; automatic 301 when a published page or post changes its address (loops and chains cleaned up). Only would-be 404s are redirected; targets must be a path or an http(s) URL.
-
Page cache for anonymous visitors that respects scheduled publishing: entries expire when the next scheduled post or page goes live and are cleared by any content, menu, settings, plugin or theme change. Pages with forms, Livewire components, flashed messages or extra cookies, signed-in users, theme previews and unknown query keys are never cached.
X-Press-Cacheheader,php artisan press:page-cache status|clear,PRESS_PAGE_CACHE,PRESS_PAGE_CACHE_TTL,PRESS_PAGE_CACHE_STORE. -
Child themes:
"parent"intheme.jsoninherits views, assets, stylesheet, settings and templates.theme_asset()helper. -
Plugin dependencies:
requires.plugins,requires.php,requires.extensions(themes: PHP and extensions). Activation, deactivation and uninstall respect them; dependencies boot first; a plugin whose dependency vanished is skipped and reported. -
Update notices for the core (GitHub latest release), plugins and themes (
update_url):php artisan press:check-updates, scheduled daily; badges in the admin. Nothing is downloaded or installed. -
Two-factor sign-in for the admin: profile page with authenticator app and recovery codes;
PRESS_REQUIRE_2FA. -
Larastan (PHPStan level 5) in CI,
composer analyse.
Security
Review of the 1.1 code paths (sign-in and 2FA, page cache keys, uploads, plugin loading, languages, redirects):
- Stored XSS from editors closed: post bodies and Text blocks are sanitised on output (Symfony HtmlSanitizer via Filament), so an editor's HTML can no longer run scripts for visitors or signed-in admins.
- SVG uploads with scripts, event handlers,
javascript:links, foreign HTML, entities or external references are refused and deleted (they are served from the site's own address). - Trusted hosts: outside
local/testing only theAPP_URLhost and its subdomains are answered, so a forgedHostheader cannot fill the page cache or poison absolute links. Make sureAPP_URLis your real address in production. - Redirect targets are limited to site paths and http(s) URLs (no
//host, no script URLs); thelocalesent by forms is accepted only when it is a configured language; language codes inPRESS_LOCALESare validated.
Changed
- symfony/yaml 8.1.9 (CVE-2026-109413).
composer.jsonnow requires PHP^8.4(the 1.0 lock file already needed it through Symfony 8.1;composer create-projecton PHP 8.3 now fails with a clear message instead of a dependency error).- Contact Form plugin 1.2.0 (sends the page language so errors are translated; strings translatable), SEO Sitemap plugin 1.2.0 (every language,
xhtml:linkalternates). - Core views use
press_route()/$model->url()instead ofroute('blog')etc. Theme overrides that still callroute('blog')keep working but always link to the default language. - Larastan baseline down from 41 to 19 entries (content models are typed).
Upgrade notes (from 1.0.0)
- Back up the database, then
composer installand runphp artisan migrate: 2FA columns onusers,locale+translation_groupon pages/posts/categories (existing rows get the default language), slug/path uniqueness becomes per language,localeon menus, and theredirectstable. All migrations are reversible. - Nothing changes for visitors until you list a second language: without
PRESS_LOCALESthe site stays single-language. To add Bulgarian setPRESS_LOCALES=en,bg, runphp artisan config:clear(androute:cacheif you cache routes), then translate items with Translate. - Production:
APP_URLmust match the address visitors use (trusted hosts). - Custom theme views: replace
route('blog'),route('posts.show', $post->slug),route('categories.show', …)andurl('/')withpress_route('blog'),$post->url(),$category->url()andpress_locale_url('/'), and add@include('press.partials.language-switcher')to your header. Wrap fixed texts in__(). - The page cache is on when
APP_DEBUG=false. Runphp artisan press:page-cache clearafter deploying view or theme files, or setPRESS_PAGE_CACHE=false. - Copy the bundled
plugins/contact-formandplugins/seo-sitemapfolders over yours (versions 1.2.0); they upgrade on the next request. - Add the Laravel scheduler to cron for daily update checks:
* * * * * php artisan schedule:run.
