chore: update kirby-seo plugin to v2.0.0-alpha.12

Update plugin from v1.1.2 to v2.0.0-alpha.12 for Kirby 5 compatibility.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
isUnknown 2026-02-10 16:23:23 +01:00
parent ff215de723
commit 04a14a7f1f
70 changed files with 6142 additions and 3 deletions

View file

@ -0,0 +1,114 @@
<?php
use Kirby\Toolkit\Str;
/** @var \Kirby\Cms\Page $page
** @var \Kirby\Cms\Site $site */
$contentHtml = $page->render();
if ($contentHtml !== '' && class_exists('DOMDocument')) {
$dom = new \DOMDocument('1.0', 'UTF-8');
$previousLibxmlState = libxml_use_internal_errors(true);
$encoded = mb_encode_numericentity($contentHtml, [0x80, 0x10FFFF, 0, 0x1FFFFF], 'UTF-8');
$loaded = $dom->loadHTML('<?xml encoding="UTF-8"?>' . $encoded, LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_NONET);
libxml_clear_errors();
libxml_use_internal_errors($previousLibxmlState);
if ($loaded !== false) {
foreach (
[
'script',
'style',
'noscript',
'template',
'svg',
'canvas',
'iframe',
'video',
'audio',
'object',
'embed',
'source',
'track',
'nav',
'footer',
'aside',
'form',
'button',
'input',
'select',
'textarea',
'label',
'menu',
'header',
] as $tag
) {
$nodes = $dom->getElementsByTagName($tag);
for ($i = $nodes->length - 1; $i >= 0; $i--) {
$node = $nodes->item($i);
if ($node !== null && $node->parentNode !== null) {
$node->parentNode->removeChild($node);
}
}
}
$xpath = new \DOMXPath($dom);
foreach (
[
'navigation',
'banner',
'contentinfo',
'complementary',
'search',
'menu',
'menubar',
'toolbar',
] as $role
) {
$nodes = $xpath->query("//*[@role='{$role}']");
if ($nodes === false) {
continue;
}
foreach ($nodes as $node) {
if ($node->parentNode !== null) {
$node->parentNode->removeChild($node);
}
}
}
$body = $dom->getElementsByTagName('body')->item(0) ?? $dom->documentElement;
if ($body instanceof \DOMNode) {
$innerHtml = '';
foreach ($body->childNodes as $child) {
$innerHtml .= $dom->saveHTML($child);
}
if ($innerHtml !== '') {
$contentHtml = $innerHtml;
}
}
}
}
$blockClosingPattern = 'p|div|section|article|main|aside|header|footer|li|ul|ol|dl|blockquote|pre|figure|figcaption|h[1-6]|table|thead|tbody|tfoot|tr|td|th|dd|dt';
$contentHtml = preg_replace('~<(?:br|hr)\b[^>]*?>~i', "\n", $contentHtml);
$contentHtml = preg_replace('~</(?:' . $blockClosingPattern . ')>~i', "\n", $contentHtml);
$text = Str::unhtml($contentHtml);
$text = Str::replace($text, "\r", "\n");
$text = preg_replace("/[ \t\x{00A0}\x{202F}\x{2007}\x{2060}]+/u", ' ', $text);
$text = preg_replace("/ *\n+ */", "\n", $text);
$text = preg_replace("/\n{3,}/", "\n\n", $text);
$content = trim($text);
?>
<content>
<?= htmlspecialchars($content, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>
</content>

View file

@ -0,0 +1,46 @@
<?php
/** @var \Kirby\Cms\Page $page
** @var \Kirby\Cms\Site $site
** @var string|null $instructions
** @var string|null $edit */ ?>
<role>
You are a professional SEO copywriter for <?= $site->title() ?>. Create high-quality content. Mimic the site's tone and style. You'll be rewarded based on the conversion rate.
</role>
<rules>
- You MUST only output the answer without additional prose or introduction.
- You MUST mimic the site's tone and style. DO NOT shift register (informal stays informal).
- The output language MUST be <language><?= $site->lang() ?></language>. Translate the content into <?= $site->lang() ?>.
- ALWAYS and ONLY provide exactly one answer. DO NOT suggest multiple answers.
- NEVER output any formatting. No new lines, no HTML tags, no quotes, no markdown.
- NEVER output or introduce information that is not provided in the content.
- NEVER output duplicate content in the same answer.
</rules>
<?php if (isset($edit) && $edit !== null && $edit !== '') : ?>
<primary-editing-task>
YOU ARE EDITING EXISTING CONTENT - NOT CREATING NEW CONTENT.
Current text that needs editing:
<current-value><?= $edit ?></current-value>
CRITICAL EDITING RULES:
- Start from the text above and modify ONLY what is requested
- Preserve as much of the original as possible
- Keep the same style, tone, and structure
- Change ONLY the specific parts mentioned in the instructions below
</primary-editing-task>
<?php endif ?>
<?php if (isset($instructions) && $instructions !== null && $instructions !== '') : ?>
<user-instructions>
<?php if (isset($edit) && $edit !== null && $edit !== '') : ?>
Apply ONLY these changes to the text above:
<?php else : ?>
The user has provided these specific instructions:
<?php endif ?>
<?= $instructions ?>
</user-instructions>
<?php endif ?>

View file

@ -0,0 +1,28 @@
<?php
/** @var \Kirby\Cms\Page $page
** @var \Kirby\Cms\Site $site
** @var string|null $currentField */
$meta = $page->metadata();
$currentField = $currentField ?? null;
$metaFields = [
'metaTitle' => 'Meta Title',
'metaDescription' => 'Meta Description',
'ogTitle' => 'Open Graph Title',
'ogDescription' => 'Open Graph Description'
];
?>
<existing-metadata>
<?php foreach ($metaFields as $key => $label) : ?>
<?php
$value = $meta->get($key);
if ($currentField === $key || !$value || $value === '') {
continue;
}
?>
<<?= $key ?>><?= $value ?></<?= $key ?>>
<?php endforeach ?>
</existing-metadata>

View file

@ -0,0 +1,25 @@
<?php
/** @var \Kirby\Cms\Site $site
** @var string|null $currentField */
$currentField = $currentField ?? null;
$metaFields = [
'metaDescription' => 'Site Meta Description',
'ogDescription' => 'Site Open Graph Description',
'ogSiteName' => 'Site Name'
];
?>
<existing-site-metadata>
<?php foreach ($metaFields as $key => $label) : ?>
<?php
$value = $site->$key()->value();
if ($currentField === $key || !$value || $value === '') {
continue;
}
?>
<<?= $key ?>><?= $value ?></<?= $key ?>>
<?php endforeach ?>
</existing-site-metadata>

View file

@ -0,0 +1,24 @@
<?php
/** @var \Kirby\Cms\Page $page
** @var \Kirby\Cms\Site $site
** @var string|null $instructions
** @var string|null $edit */
$meta = $page->metadata();
snippet('seo/prompts/introduction', [
'instructions' => $instructions ?? null,
'edit' => $edit ?? null
]); ?>
<task>
Create a useful meta description for this page called <page-title><?= $page->title()->value() ?></page-title>. <?php if ($page->isHomePage()) : ?>This page is the homepage of the website.<?php endif ?>
The entire meta description SHOULD be between 120 and 158 characters long.
You'll receive the content of the page as well as any meta tags that are already set below.
</task>
<?php snippet('seo/prompts/meta', ['currentField' => 'metaDescription']);
snippet('seo/prompts/content');

View file

@ -0,0 +1,25 @@
<?php
/** @var \Kirby\Cms\Page $page
** @var \Kirby\Cms\Site $site
** @var string|null $instructions
** @var string|null $edit */
$meta = $page->metadata();
snippet('seo/prompts/introduction', [
'instructions' => $instructions ?? null,
'edit' => $edit ?? null
]); ?>
<task>
Create a useful open graph description for this page called <page-title><?= $page->title()->value() ?></page-title>. <?php if ($page->isHomePage()) : ?>This page is the homepage of the website.<?php endif ?>
This description will be shown on social media platforms like Facebook, WhatsApp and LinkedIn.
The entire meta description SHOULD be between 120 and 158 characters long.
You'll receive the content of the page as well as any meta tags that are already set below.
</task>
<?php snippet('seo/prompts/meta', ['currentField' => 'ogDescription']);
snippet('seo/prompts/content');

View file

@ -0,0 +1,25 @@
<?php
/** @var \Kirby\Cms\Page $page
** @var \Kirby\Cms\Site $site
** @var string|null $instructions
** @var string|null $edit */
snippet('seo/prompts/introduction', [
'instructions' => $instructions ?? null,
'edit' => $edit ?? null
]); ?>
<task>
Create a useful GLOBAL open graph description for this site <site-title><?= $site->title()->value() ?>.</site-title>
This description will be shown on social media platforms like Facebook, WhatsApp and LinkedIn.
This description is meant as FALLBACK for when the page does not have a meta description itself.
This description should be unique and relevant to the site's content.
The entire meta description SHOULD be between 120 and 158 characters long.
You'll receive the content of the home page as well as any meta tags that are already set below.
</task>
<?php snippet('seo/prompts/site-meta', ['currentField' => 'ogDescription']);
snippet('seo/prompts/content'); ?>

View file

@ -0,0 +1,24 @@
<?php
/** @var \Kirby\Cms\Page $page
** @var \Kirby\Cms\Site $site
** @var string|null $instructions
** @var string|null $edit */
snippet('seo/prompts/introduction', [
'instructions' => $instructions ?? null,
'edit' => $edit ?? null
]); ?>
<task>
Create a useful GLOBAL meta description for this site <site-title><?= $site->title()->value() ?>.</site-title>
This description is meant as FALLBACK for when the page does not have a meta description itself.
This description should be unique and relevant to the site's content.
The entire meta description SHOULD be between 120 and 158 characters long.
You'll receive the content of the home page as well as any meta tags that are already set below.
</task>
<?php snippet('seo/prompts/site-meta', ['currentField' => 'metaDescription']);
snippet('seo/prompts/content'); ?>

View file

@ -0,0 +1,39 @@
<?php
use Kirby\Toolkit\Str;
/** @var \Kirby\Cms\Page $page
** @var \Kirby\Cms\Site $site
** @var string|null $instructions
** @var string|null $edit */
$meta = $page->metadata();
snippet('seo/prompts/introduction', [
'instructions' => $instructions ?? null,
'edit' => $edit ?? null
]); ?>
<task>
Create a useful meta title for this page called <page-title><?= $page->title()->value() ?></page-title>. <?php if ($page->isHomePage()) : ?>This page is the homepage of the website. AVOID an overly generic title such as 'Home'.<?php endif ?>
<?php if ($page->useTitleTemplate()->isEmpty() ? true : $page->useTitleTemplate()->toBool()):
$template = $meta->get('metaTemplate');
$templatePreview = $page->toString($template, ['title' => '{{ title }}']);
$templateBaseLength = Str::length($page->toString($template, ['title' => '']));
?>
The final page title will be rendered as:
<template><?= $templatePreview ?></template>
Where {{ title }} is your page title. The entire title SHOULD be between <?= max(0, 50 - $templateBaseLength) ?>-<?= max(max(0, 50 - $templateBaseLength), 60 - $templateBaseLength) ?> characters long.
DO NOT output the Title Template. ONLY output what should be placed inside {{ title }}. DO NOT repeat ANYTHING that exists in the template. You MUST NOT repeat the name of the site.
<?php else: ?>
Your response will be set as title without any changes. The entire title SHOULD be between 50-60 characters long.
<?php endif; ?>
If useful for the customers niche, include a keyword for the location. AVOID for global companies or niche subpages.
</task>
<?php snippet('seo/prompts/meta', ['currentField' => 'metaTitle']);
snippet('seo/prompts/content');