arout / seo-toolkit-pro
Pro tier for arout/seo-toolkit: on-page audit dashboard, readability scoring, 404 monitor, redirect manager, internal linking suggestions, and analytics tag manager.
Package info
github.com/arout77/SEO-Toolkit-Pro
Type:rhapsody-module
pkg:composer/arout/seo-toolkit-pro
Requires
- php: >=8.4
- arout/seo-toolkit: ^1.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Pro tier for arout/seo-toolkit. Depends on it via Composer; ships as its
own module (seo-toolkit-pro slug) rather than a license-flag inside Core.
Features
- On-page audit dashboard — title/meta-description length, H1 count, image alt coverage, canonical presence, per crawled page.
- Readability analysis — Flesch Reading Ease score per page.
- 404 monitor — every real 404 hit logged (URL, referrer, IP, UA, timestamp), aggregated on display, one-click promote to a redirect.
- Redirect manager — 301/302 table, matched before falling through to a real 404.
- Internal linking suggestions — keyword-overlap between page title/meta-description (deliberately the simple version — full content-similarity is a future upgrade, no content/tagging model exists in Rhapsody yet to build a stronger version on).
- Analytics tag manager — GA4, Meta Pixel, and a generic custom-script field, injected via a Twig function themes opt into explicitly.
Setup
composer require arout/seo-toolkit-pro- Run
php rhapsody module:install arout/seo-toolkit-pro, which runsModuleProvider::install()— creates the module's four tables. - Add
{{ seo_analytics_scripts()|raw }}near</head>in every theme layout you want analytics tags to appear on — same opt-in pattern as{{ schema_markup|raw }}. - Visit
/seo-toolkit-pro/settingsto configure GA4 / Meta Pixel / custom script,/seo-toolkit-pro/sample-urlsto register concrete URLs for any parameterized routes (product pages, article pages, etc.) so the crawler has something to request, then/seo-toolkit-pro/auditand hit "Run Scan".
What's assumed, not confirmed — check these against the real source
This was built without direct access to the Rhapsody Core source, working
from what's already documented/decided about it. Everything below is a
best-guess against those conventions and is flagged inline with NOTE:
comments at the point it's used:
ModuleContext's namespace — now confirmed. It'sRhapsody\Core\Modules\ModuleContext, notRhapsody\Core\Contracts\ModuleContextas first guessed (that guess matched whereSkeletonMigrationInterfacelives, but the module system has its ownModules\namespace root — consistent withModuleServiceProviderInterfaceandDatabaseFacadeboth living underRhapsody\Core\Modules\...too). Its actual facade methods beyonddatabase()(now fully confirmed, below) —events(),routes(),settings(),twig()— still haven't been checked against source.RouteNotFoundevent shape — confirmed: implementsStoppableEventInterface, listener callssetResponse(Response)to stop propagation. Assumed but unconfirmed: it also exposesgetRequest().- Internal request dispatch for the crawler (
RouteCrawler::render()) — assumedRequest::createInternal()and a container-resolvedRouter::dispatch()exist for firing a request without going over real HTTP. This is the single riskiest assumption in the package — if Core has no way to dispatch a synthetic in-process request, the crawler needs a different approach (e.g. shelling out tofile_get_contents()against the live site instead of dispatching internally). - Routes table shape — assumed
$routes->registered()returns['method' => ..., 'path' => ...]rows, mirroring what the dynamic sitemap generator in Core SEO Toolkit already reads. - Module controller base class — no shared base class name confirmed
for module-owned controllers (only that app-level controllers extend
BaseController).ProDashboardControllercurrently has inline placeholderview()/redirect()methods standing in for whatever the real base gives modules — swap those out once confirmed. DatabaseFacade's real API — now confirmed from source, no longer a guess. It's flat, not fluent:select($table, $where, $columns),insert($table, $data),update($table, $data, $where),delete($table, $where)(the last two require a non-empty$where),query($sql)for read-only SQL on any table (used innotFoundLog()for the one place ordering was needed, sinceselect()has no order-by), andmigrate($sql)for schema — oneCREATE/ALTER/DROP TABLEstatement per call, table name extracted by regex to enforce the module can only touch its ownmod_arout_seo_toolkit_pro_*tables. There's noupsert(), sorunScan()does a manual select-then-insert-or-update. Schema now lives directly inModuleProvider::install()/uninstall()as fourmigrate()calls rather than a separate.sqlfile, sinceinstall()/uninstall()turned out to be the real lifecycle hooks for this (see below) andmigrate()only takes one statement at a time anyway.register()vsboot()split — confirmed the interface has both (plusinstall()/uninstall(), confirmed by a live fatal error naming all four as required). Current guess, now indirectly supported byinstall()running successfully up to theraw()/migrate()line:register()for bindings with no cross-module dependencies,boot()for anything that needs every module already registered — routes, the event listener, and the Twig function registration all live inboot().
None of these change the actual logic (crawl → analyze → store; check redirects → else log 404; expose analytics scripts via Twig) — they're all isolated to the handful of lines that talk to Core, so point out the real signatures and they're quick fixes.