chatixy / kirby-chatixy
Chatixy AI support agent for Kirby - site-wide install with no template changes.
Requires
- php: >=8.0
- getkirby/composer-installer: ^1.2
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Adds the Chatixy AI support agent to every rendered HTML
page of a Kirby site. Install it, put your widget key in
site/config/config.php, done. No template changes, no snippet to include, no
js() call to add.
Requirements
| Kirby | 4 and 5 |
| PHP | 8.0+ (composer.json requires "php": ">=8.0") |
| Composer | optional - the plugin loads its own classes when dropped in by hand |
Install
Composer (recommended)
composer require chatixy/kirby-chatixy
Manual
Unzip the package into site/plugins/chatixy/ in your Kirby project, so that
index.php sits at site/plugins/chatixy/index.php. The plugin require_onces
its own two classes, so it works with no Composer autoloading at all.
Configure
Add the widget key to site/config/config.php. The exact option keys are
chatixy.chatixy.widgetKey and chatixy.chatixy.enabled - Kirby namespaces
plugin options as <vendor>.<plugin>.<option>, and this plugin registers itself
as chatixy/chatixy:
<?php return [ 'chatixy.chatixy.widgetKey' => 'a1b2c3d4...', // 64-hex key from your Chatixy dashboard, Install tab 'chatixy.chatixy.enabled' => true, // optional, defaults to true ];
The key option accepts any of the three shapes the dashboard hands out:
- the bare 64-character key,
<key>.js,- the whole
<script src="https://chatixy.com/source/<key>.js" async></script>snippet.
The key is extracted from whatever you paste. Anything that does not contain a 64-hex key - including leaving the option out entirely - injects nothing at all rather than emitting a broken tag.
Set chatixy.chatixy.enabled to false to switch the support agent off without
deleting the key. The check is a strict comparison against boolean true, so
give it a real boolean; any other value counts as off.
No Chatixy account yet? Start at https://chatixy.com/register.
What it does
On every rendered HTML page it splices one tag in before the closing </body>:
<script src="https://chatixy.com/source/<key>.js?platform=kirby" async></script>
That is the whole payload. Nothing is added to <head>, no CSS, no extra
requests from the server side.
The injection point is Kirby's page.render:after hook, which is handed the
finished document and uses whatever it returns as the response body. That is
what makes site-wide injection possible from a third-party plugin: your
templates and snippets never have to cooperate.
What it rewrites, and what it leaves alone
The rewrite is deliberately total - for every input it returns either a correctly modified document or the original string unchanged:
| Situation | Behaviour |
|---|---|
Content type is not html (JSON, XML, txt) |
returned byte-identical |
| Widget key missing, or not a 64-hex key | injects nothing |
Document has no </body> |
returned unchanged, never appended to |
| The loader for this key is already on the page | returned unchanged, no second tag |
The content-type gate matters most: Kirby serves JSON and XML representations
(sitemap.xml, a feed, any .json.php template) through the very same hook, and
appending a <script> to those would produce an unparseable payload. The gate
runs before anything else, so a </body> inside a JSON string cannot trigger a
splice.
The </body> search takes the last occurrence, so an escaped
</body> inside a code sample on the page cannot win over the real end of
the document. Idempotence is keyed on the loader path rather than on a marker
attribute, so a snippet someone pasted into a template by hand counts as
"already present" too - exactly one support agent per page, ever.
Security: the origin is pinned
The widget key is public; the host is not configurable. There is no host
option and no constant to override, because the stored host would build a
first-party <script src> on every page of the site - i.e. site-wide stored XSS
if it could be influenced, and config.php is a plain PHP file that a deploy
pipeline or a stray include can write. ChatixyKey::sanitizeHost() accepts only
an https origin whose host is chatixy.com or a subdomain of it, and returns
the canonical origin for anything else. The pattern is anchored at both ends and
only grows the host to the left of a literal dot, so evilchatixy.com,
chatixy.com.evil.example, chatixy.com@evil.example and http://chatixy.com
are all rejected. The finished src is HTML-escaped into the attribute on top of
that.
Support
- Docs and account: https://chatixy.com
- Help: https://chatixy.com/support
- Email: support@chatixy.com
MIT licensed. See LICENSE.