getkirby / kql
Kirby 查询语言
Requires
- getkirby/cms: ^3.10.0 || ^4.0 || ^5.0
- getkirby/composer-installer: ^1.2.1
This package is auto-updated.
Last update: 2024-09-09 12:01:21 UTC
README
Kirby 的查询语言 API 结合了 Kirby 数据结构的灵活性、GraphQL 的强大功能和 REST 的简单性。
Kirby QL API 接收标准的 JSON 对象的 POST 请求,并返回高度定制的符合您应用程序的结果。
沙盒
您可以在我们的 KQL 沙盒 中玩耍。沙盒基于 Kirby Starterkit。
ℹ️ 沙盒的源代码可在 GitHub 上获取。
示例
给定一个对 /api/query
的 POST 请求
{ "query": "page('photography').children", "select": { "url": true, "title": true, "text": "page.text.markdown", "images": { "query": "page.images", "select": { "url": true } } }, "pagination": { "limit": 10 } }
🆗 响应
{ "code": 200, "result": { "data": [ { "url": "https://example.com/photography/trees", "title": "Trees", "text": "Lorem <strong>ipsum</strong> …", "images": [ { "url": "https://example.com/media/pages/photography/trees/1353177920-1579007734/cheesy-autumn.jpg" }, { "url": "https://example.com/media/pages/photography/trees/1940579124-1579007734/last-tree-standing.jpg" }, { "url": "https://example.com/media/pages/photography/trees/3506294441-1579007734/monster-trees-in-the-fog.jpg" } ] }, { "url": "https://example.com/photography/sky", "title": "Sky", "text": "<h1>Dolor sit amet</h1> …", "images": [ { "url": "https://example.com/media/pages/photography/sky/183363500-1579007734/blood-moon.jpg" }, { "url": "https://example.com/media/pages/photography/sky/3904851178-1579007734/coconut-milkyway.jpg" } ] } ], "pagination": { "page": 1, "pages": 1, "offset": 0, "limit": 10, "total": 2 } }, "status": "ok" }
安装
手动
下载 并将此存储库复制到您的 Kirby 安装中的 /site/plugins/kql
。
Composer
composer require getkirby/kql
文档
API 端点
KQL 为您的 Kirby API 添加了一个新的 query
API 端点(即 yoursite.com/api/query
)。此端点 需要身份验证。
您可以在自己的风险下禁用配置中的身份验证
return [ 'kql' => [ 'auth' => false ] ];
发送 POST 请求
您可以使用您选择的任何 HTTP 请求库向您的 /api/query
端点发送常规 POST 请求。在此示例中,我们使用 fetch API 和 JavaScript 从我们的 Kirby 安装中检索数据。
const api = "https://yoursite.com/api/query"; const username = "apiuser"; const password = "strong-secret-api-password"; const headers = { Authorization: "Basic " + Buffer.from(`${username}:${password}`).toString("base64"), "Content-Type": "application/json", Accept: "application/json", }; const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "page('notes').children", select: { title: true, text: "page.text.kirbytext", slug: true, date: "page.date.toDate('d.m.Y')", }, }), headers, }); console.log(await response.json());
查询
使用查询,您可以从 Kirby 站点的任何地方获取数据。您可以查询字段、页面、文件、用户、语言、角色等。
没有选择器的查询
当您不传递选择器选项时,Kirby 会尝试为您生成最有用的结果集。这对于简单的查询非常棒。
获取站点标题
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "site.title", }), headers, }); console.log(await response.json());
🆗 响应
{ code: 200, result: "Kirby Starterkit", status: "ok" }
获取页面 ID 列表
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "site.children", }), headers, }); console.log(await response.json());
🆗 响应
{ code: 200, result: [ "photography", "notes", "about", "error", "home" ], status: "ok" }
运行字段方法
查询甚至可以执行字段方法。
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "site.title.upper", }), headers, }); console.log(await response.json());
🆗 响应
{ code: 200, result: "KIRBY STARTERKIT", status: "ok" }
选择
KQL 通过其灵活的选择器选项控制结果集的方式变得非常强大。
选择单个属性和字段
要包含属性或字段在您的结果中,将它们列为一个数组。请参阅我们的 参考,了解页面、用户、文件等可用的属性。
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "site.children", select: ["title", "url"], }), headers, }); console.log(await response.json());
🆗 响应
{ code: 200, result: { data: [ { title: "Photography", url: "/photography" }, { title: "Notes", url: "/notes" }, { title: "About us", url: "/about" }, { title: "Error", url: "/error" }, { title: "Home", url: "/" } ], pagination: { page: 1, pages: 1, offset: 0, limit: 100, total: 5 } }, status: "ok" }
您还可以使用对象表示法,并传递您要包含的每个键/属性的 true 值。
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "site.children", select: { title: true, url: true, }, }), headers, }); console.log(await response.json());
🆗 响应
{ code: 200, result: { data: [ { title: "Photography", url: "/photography" }, { title: "Notes", url: "/notes" }, { title: "About us", url: "/about" }, { title: "Error", url: "/error" }, { title: "Home", url: "/" } ], pagination: { ... } }, status: "ok" }
使用查询进行属性和字段
除了传递 true,您还可以传递一个字符串查询以指定您要在 select 对象中的每个键上返回的内容。
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "site.children", select: { title: "page.title", }, }), headers, }); console.log(await response.json());
🆗 响应
{ code: 200, result: { data: [ { title: "Photography", }, { title: "Notes", }, ... ], pagination: { ... } }, status: "ok" }
执行字段方法
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "site.children", select: { title: "page.title.upper", }, }), headers, }); console.log(await response.json());
🆗 响应
{ code: 200, result: { data: [ { title: "PHOTOGRAPHY", }, { title: "NOTES", }, ... ], pagination: { ... } }, status: "ok" }
创建别名
字符串查询是创建别名或返回同一字段或属性的多个变体的一种完美方式。
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "page('notes').children", select: { title: "page.title", upperCaseTitle: "page.title.upper", lowerCaseTitle: "page.title.lower", guid: "page.id", date: "page.date.toDate('d.m.Y')", timestamp: "page.date.toTimestamp", }, }), headers, }); console.log(await response.json());
🆗 响应
{ code: 200, result: { data: [ { title: "Explore the universe", upperCaseTitle: "EXPLORE THE UNIVERSE", lowerCaseTitle: "explore the universe", guid: "notes/explore-the-universe", date: "21.04.2018", timestamp: 1524316200 }, { ... }, { ... }, ... ], pagination: { ... } }, status: "ok" }
子查询
使用此类字符串查询,您当然也可以包含嵌套数据
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "page('photography').children", select: { title: "page.title", images: "page.images", }, }), headers, }); console.log(await response.json());
🆗 响应
{ code: 200, result: { data: [ { title: "Trees", images: [ "photography/trees/cheesy-autumn.jpg", "photography/trees/last-tree-standing.jpg", "photography/trees/monster-trees-in-the-fog.jpg", "photography/trees/sharewood-forest.jpg", "photography/trees/stay-in-the-car.jpg" ] }, { ... }, { ... }, ... ], pagination: { ... } }, status: "ok" }
带有选择器的子查询
您还可以传递一个包含 query
和 select
选项的对象
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "page('photography').children", select: { title: "page.title", images: { query: "page.images", select: { filename: true, }, }, }, }), headers, }); console.log(await response.json());
🆗 响应
{ code: 200, result: { data: [ { title: "Trees", images: { { filename: "cheesy-autumn.jpg" }, { filename: "last-tree-standing.jpg" }, { filename: "monster-trees-in-the-fog.jpg" }, { filename: "sharewood-forest.jpg" }, { filename: "stay-in-the-car.jpg" } } }, { ... }, { ... }, ... ], pagination: { ... } }, status: "ok" }
分页
无论您查询哪个集合(页面、文件、用户、角色、语言),都可以限制结果集并通过条目进行分页。您可能已经在上面的结果中看到了分页对象。它包含在所有集合结果中,即使您没有指定任何分页设置。
限制
您可以使用“限制”选项指定自定义限制。集合的默认限制为100条条目。
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "page('notes').children", pagination: { limit: 5, }, select: { title: "page.title", }, }), headers, }); console.log(await response.json());
🆗 响应
{ code: 200, result: { data: [ { title: "Across the ocean" }, { title: "A night in the forest" }, { title: "In the jungle of Sumatra" }, { title: "Through the desert" }, { title: "Himalaya and back" } ], pagination: { page: 1, pages: 2, offset: 0, limit: 5, total: 7 } }, status: "ok" }
页面
您可以使用page
选项跳转到结果集中的任何页面。
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "page('notes').children", pagination: { page: 2, limit: 5, }, select: { title: "page.title", }, }), headers, }); console.log(await response.json());
🆗 响应
{ code: 200, result: { data: [ { title: "Chasing waterfalls" }, { title: "Exploring the universe" } ], pagination: { page: 2, pages: 2, offset: 5, limit: 5, total: 7 } }, status: "ok" }
子查询中的分页
分页设置也适用于子查询。
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "page('photography').children", select: { title: "page.title", images: { query: "page.images", pagination: { page: 2, limit: 5, }, select: { filename: true, }, }, }, }), headers, }); console.log(await response.json());
单个调用中的多个查询
凭借选择和子查询的强大功能,您可以在单个请求中基本上查询整个网站。
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "site", select: { title: "site.title", url: "site.url", notes: { query: "page('notes').children.listed", select: { title: true, url: true, date: "page.date.toDate('d.m.Y')", text: "page.text.kirbytext", }, }, photography: { query: "page('photography').children.listed", select: { title: true, images: { query: "page.images", select: { url: true, alt: true, caption: "file.caption.kirbytext", }, }, }, }, about: { text: "page.text.kirbytext", }, }, }), headers, }); console.log(await response.json());
允许方法
默认情况下,KQL对允许的方法非常严格。不允许自定义页面方法、文件方法或模型方法,以确保您不会意外遗漏重要的安全问题。尽管如此,您仍然可以允许额外的方法。
允许列表
最直接的方法是在您的配置中定义允许的方法。
return [ 'kql' => [ 'methods' => [ 'allowed' => [ 'MyCustomPage::cover' ] ] ] ];
DocBlock 注释
您还可以在方法的doc块中添加注释来允许它们。
class MyCustomPage extends Page { /** * @kql-allowed */ public function cover() { return $this->images()->findBy('name', 'cover') ?? $this->image(); } }
这同样适用于模型方法、自定义页面方法、文件方法或其他在插件中定义的方法。
Kirby::plugin('your-name/your-plugin', [ 'pageMethods' => [ /** * @kql-allowed */ 'cover' => function () { return $this->images()->findBy('name', 'cover') ?? $this->image(); } ] ]);
阻止方法
您可以通过在配置中列出它们来阻止个别类方法,这些方法通常可以通过配置访问。
return [ 'kql' => [ 'methods' => [ 'blocked' => [ 'Kirby\Cms\Page::url' ] ] ] ];
阻止类
有时您可能想减少对系统各个部分的访问。这可以通过阻止个别方法(见上文)或阻止整个类来实现。
return [ 'kql' => [ 'classes' => [ 'blocked' => [ 'Kirby\Cms\User' ] ] ] ];
现在,任何用户的访问都被阻止了。
自定义类和拦截器
如果您想添加对自定义类或Kirby源中尚未支持类的支持,您可以在配置中列出您自己的拦截器。
return [ 'kql' => [ 'interceptors' => [ 'Kirby\Cms\System' => 'SystemInterceptor' ] ] ];
例如,您可以将此类自定义拦截器的类放在插件中。
class SystemInterceptor extends Kirby\Kql\Interceptors\Interceptor { public const CLASS_ALIAS = 'system'; protected $toArray = [ 'isInstallable', ]; public function allowedMethods(): array { return [ 'isInstallable', ]; } }
拦截器类非常直观。使用CLASS_ALIAS
,您可以为KQL查询中具有此类对象的实例提供一个简短名称。如果未运行子查询,则$toArray
属性列出应渲染的所有方法。例如,在这种情况下,kirby.system
将渲染一个包含isInstallable
值的数组。
allowedMethods
方法必须返回一个数组,其中包含可以访问此对象的所有方法。除此之外,您还可以在拦截器中创建自己的自定义方法,这些方法随后将在KQL中可用。
class SystemInterceptor extends Kirby\Kql\Interceptors\Interceptor { ... public function isReady() { return 'yes it is!'; } }
现在,此自定义方法可以在KQL中使用kirby.system.isReady
,并返回yes it is!
。
未拦截的类
如果您想在没有任何拦截器的情况下完全允许对整个类的访问,您可以将该类添加到配置中的允许列表中。
return [ 'kql' => [ 'classes' => [ 'allowed' => [ 'Kirby\Cms\System' ] ] ] ];
这将引入对所有公共类方法的完全访问。但这非常危险,因此您应尽可能避免这样做。
无变异
KQL仅提供对网站数据的访问。它不支持任何变异。所有具有破坏性的方法都被阻止,无法在查询中访问。
插件
什么是Kirby?
- getkirby.com – 了解CMS。
- 试用 – 使用我们的在线演示进行测试。或者下载我们的套件开始使用。
- 文档 – 阅读官方指南、参考和食谱。
- 问题 – 报告错误和其他问题。
- 反馈 – 您对Kirby有什么想法?分享它。
- 论坛 – 无论何时遇到困难,都不要犹豫,寻求问题和支持。
- Discord – 在线聚会,结识社区成员。
- Mastodon – 传播信息。
- Instagram – 分享您的创作:#madewithkirby。
许可协议
MIT 许可协议 © 2020-2023 Bastian Allgeier