├── .editorconfig
├── .gitattributes
├── .gitignore
├── LICENSE.md
├── README.md
├── composer.json
├── composer.lock
├── index.php
└── src
├── SearchResult.php
├── TypesenseClient.php
├── TypesenseCommand.php
├── TypesenseConfig.php
├── TypesenseDocument.php
├── TypesenseException.php
├── TypesenseItem.php
├── TypesenseItemInterface.php
└── TypesenseSearch.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs
2 | # editorconfig.org
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | end_of_line = lf
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.php]
13 | indent_size = 4
14 |
15 | [*.md,*.txt]
16 | trim_trailing_whitespace = false
17 | insert_final_newline = false
18 |
19 | [composer.json]
20 | indent_size = 4
21 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Note: You need to uncomment the lines you want to use; the other lines can be deleted
2 |
3 | # Git
4 | # .gitattributes export-ignore
5 | # .gitignore export-ignore
6 |
7 | # Tests
8 | # /.coveralls.yml export-ignore
9 | # /.travis.yml export-ignore
10 | # /phpunit.xml.dist export-ignore
11 | # /tests/ export-ignore
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS files
2 | .DS_Store
3 |
4 | # npm modules
5 | /node_modules
6 |
7 | # files of Composer dependencies that are not needed for the plugin
8 | vendor
9 | /vendor/**/.*
10 | /vendor/**/*.json
11 | /vendor/**/*.txt
12 | /vendor/**/*.md
13 | /vendor/**/*.yml
14 | /vendor/**/*.yaml
15 | /vendor/**/*.xml
16 | /vendor/**/*.dist
17 | /vendor/**/readme.php
18 | /vendor/**/LICENSE
19 | /vendor/**/COPYING
20 | /vendor/**/VERSION
21 | /vendor/**/docs/*
22 | /vendor/**/example/*
23 | /vendor/**/examples/*
24 | /vendor/**/test/*
25 | /vendor/**/tests/*
26 | /vendor/**/php4/*
27 | /vendor/getkirby/composer-installer
28 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Maxime CHÊNE
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Kirby Typesense
2 |
3 | This plugin allow you to index kirby pages into typesense and make fulltext search available for your website.
4 |
5 | ## Overview
6 |
7 | > This plugin is free and under MIT license, I'm glad if you find it usefull. If you plan to use it for commercial use,
8 | > or if I saved you some time,
9 | > please consider helping me to maintain this by either [making a donation](https://www.paypal.com/paypalme/maximechene)
10 | > of your choice or [sponsoring me](https://github.com/sponsors/maxchene).
11 |
12 | ## 1. Install Typesense on your server
13 |
14 | ### 1.1 What is Typesense ?
15 |
16 | [Typesense](https://typesense.org) is an opensource alternative for Algolia or ElasticSearch. It is free and provide a
17 | fast typo tolerant search engine.
18 |
19 | ### 1.2 Install Typesense
20 |
21 | To use this plugin, you need
22 | to [install Typesense on your server](https://typesense.org/docs/guide/install-typesense.html).
23 |
24 |
25 |
26 | ## 2. Install the Kirby Typesense plugin
27 |
28 | Download and copy this repository to ```/site/plugins/typesense```
29 |
30 | Or even better, to get easy updates, install it with composer: ```composer require maxchene/kirby3-typesense```
31 |
32 |
33 |
34 | ## 3. Configuration
35 |
36 | Edit your config file: ```site/config/config.php``` to add your own plugin configuration.
37 |
38 | Here is what it should look like:
39 |
40 | ```php
41 | 'maxchene.typesense' => [
42 | 'host' => 'localhost:8108', # typesense host and port
43 | 'key' => 'secret', # typesense API key
44 | 'num_typos' => 2, # number of allowed typo error
45 | 'schema' => [
46 | 'name' => 'my-collection',
47 | 'fields' => [
48 | ['name' => 'content', 'type' => 'string'],
49 | ['name' => 'type', 'type' => 'string']
50 | ]
51 | ],
52 | 'templates' => [
53 | 'article' => function (Page $page) {
54 |
55 | },
56 | 'default' => function (Page $page) {
57 | return [
58 | 'content' => 'text content that should be indexed for fulltext search',
59 | 'type' => 'default'
60 | ];
61 | }
62 | ]
63 | ]
64 | ```
65 |
66 |
67 |
68 | ### 3.1 Schema configuration
69 |
70 | ### 3.2 Templates configuration
71 |
72 | ## 4. Use fulltext search
73 |
74 | All fields provided in the config file will be searched.
75 |
76 | | Param | Description | Type | Optionnal | Default value |
77 | |--------|------------------------------------|---------|-----------|---------------|
78 | | $query | your search string | string | no | |
79 | | $limit | Maximum number of results per page | integer | yes | 30 |
80 | | $page | Page number | integer | yes | 1 |
81 |
82 |
83 |
84 | ### 4.1 Site method
85 |
86 | This plugins gives you access to the ```$site->typesenseSearch(string $query, int $limit, int $page)``` method
87 | wherever ```$site``` is available: templates, controllers,...
88 |
89 |
90 |
91 | ### 4.2 Typesense Search
92 |
93 | You can also create a TypesenseSearch instance :
94 |
95 | ````php
96 | $searchEngine = new TypesenseSearch();
97 | $results = $searchEngine->search($query, $limit, $page);
98 |
99 | ````
100 |
101 | ## 5. Command Line
102 |
103 | ## 6. Full configuration exemple
104 |
105 | ## 7. TODOS
106 |
107 | - add command line tools
108 | - improve docs or enable wiki pages for this repo
109 | - maybe add a Page method to build indexed content
110 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "maxchene/kirby3-typesense",
3 | "homepage": "https://github.com/maxchene/kirby3-typesense",
4 | "description": "Kirby plugin that use typesense for quick search",
5 | "license": "MIT",
6 | "type": "kirby-plugin",
7 | "version": "1.0.2",
8 | "authors": [
9 | {
10 | "name": "Maxime CHENE",
11 | "email": "maxime@kaliel.fr"
12 | }
13 | ],
14 | "require": {
15 | "php": ">=8.1",
16 | "getkirby/composer-installer": "^1.1"
17 | },
18 | "autoload": {
19 | "psr-4": {
20 | "Maxchene\\Typesense\\": "src/"
21 | }
22 | },
23 | "config": {
24 | "optimize-autoloader": true,
25 | "allow-plugins": {
26 | "getkirby/composer-installer": true
27 | }
28 | },
29 | "extra": {
30 | "installer-name": "typesense"
31 | },
32 | "keywords": [
33 | "kirby",
34 | "kirby3",
35 | "kirby3-cms",
36 | "kirby3-plugin",
37 | "typesense",
38 | "fulltext",
39 | "search"
40 | ],
41 | "require-dev": {
42 | "symfony/var-dumper": "^6.2",
43 | "getkirby/cli": "^1.1"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "438e42e92566ee7b88ce61aa948aabbc",
8 | "packages": [
9 | {
10 | "name": "getkirby/composer-installer",
11 | "version": "1.2.1",
12 | "source": {
13 | "type": "git",
14 | "url": "https://github.com/getkirby/composer-installer.git",
15 | "reference": "c98ece30bfba45be7ce457e1102d1b169d922f3d"
16 | },
17 | "dist": {
18 | "type": "zip",
19 | "url": "https://api.github.com/repos/getkirby/composer-installer/zipball/c98ece30bfba45be7ce457e1102d1b169d922f3d",
20 | "reference": "c98ece30bfba45be7ce457e1102d1b169d922f3d",
21 | "shasum": ""
22 | },
23 | "require": {
24 | "composer-plugin-api": "^1.0 || ^2.0"
25 | },
26 | "require-dev": {
27 | "composer/composer": "^1.8 || ^2.0"
28 | },
29 | "type": "composer-plugin",
30 | "extra": {
31 | "class": "Kirby\\ComposerInstaller\\Plugin"
32 | },
33 | "autoload": {
34 | "psr-4": {
35 | "Kirby\\": "src/"
36 | }
37 | },
38 | "notification-url": "https://packagist.org/downloads/",
39 | "license": [
40 | "MIT"
41 | ],
42 | "description": "Kirby's custom Composer installer for the Kirby CMS and for Kirby plugins",
43 | "homepage": "https://getkirby.com",
44 | "support": {
45 | "issues": "https://github.com/getkirby/composer-installer/issues",
46 | "source": "https://github.com/getkirby/composer-installer/tree/1.2.1"
47 | },
48 | "funding": [
49 | {
50 | "url": "https://getkirby.com/buy",
51 | "type": "custom"
52 | }
53 | ],
54 | "time": "2020-12-28T12:54:39+00:00"
55 | }
56 | ],
57 | "packages-dev": [
58 | {
59 | "name": "getkirby/cli",
60 | "version": "1.1.1",
61 | "source": {
62 | "type": "git",
63 | "url": "https://github.com/getkirby/cli.git",
64 | "reference": "0e8074b0e6081c9e1a9f31090733b058db15be06"
65 | },
66 | "dist": {
67 | "type": "zip",
68 | "url": "https://api.github.com/repos/getkirby/cli/zipball/0e8074b0e6081c9e1a9f31090733b058db15be06",
69 | "reference": "0e8074b0e6081c9e1a9f31090733b058db15be06",
70 | "shasum": ""
71 | },
72 | "require": {
73 | "composer-runtime-api": "^2.2",
74 | "ext-zip": "*",
75 | "guzzlehttp/guzzle": "^7.5",
76 | "league/climate": "^3.8",
77 | "php": ">=8.0.0 <8.3.0"
78 | },
79 | "bin": [
80 | "bin/kirby"
81 | ],
82 | "type": "library",
83 | "autoload": {
84 | "psr-4": {
85 | "Kirby\\": [
86 | "src/",
87 | "tests/"
88 | ]
89 | }
90 | },
91 | "notification-url": "https://packagist.org/downloads/",
92 | "license": [
93 | "MIT"
94 | ],
95 | "authors": [
96 | {
97 | "name": "Kirby Team",
98 | "email": "support@getkirby.com",
99 | "homepage": "https://getkirby.com"
100 | }
101 | ],
102 | "description": "Kirby command line interface",
103 | "homepage": "https://getkirby.com",
104 | "keywords": [
105 | "cli",
106 | "cms",
107 | "command",
108 | "kirby"
109 | ],
110 | "support": {
111 | "email": "support@getkirby.com",
112 | "forum": "https://forum.getkirby.com",
113 | "issues": "https://github.com/getkirby/cli/issues",
114 | "source": "https://github.com/getkirby/cli"
115 | },
116 | "funding": [
117 | {
118 | "url": "https://getkirby.com/buy",
119 | "type": "custom"
120 | }
121 | ],
122 | "time": "2023-02-10T12:27:30+00:00"
123 | },
124 | {
125 | "name": "guzzlehttp/guzzle",
126 | "version": "7.5.0",
127 | "source": {
128 | "type": "git",
129 | "url": "https://github.com/guzzle/guzzle.git",
130 | "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba"
131 | },
132 | "dist": {
133 | "type": "zip",
134 | "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba",
135 | "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba",
136 | "shasum": ""
137 | },
138 | "require": {
139 | "ext-json": "*",
140 | "guzzlehttp/promises": "^1.5",
141 | "guzzlehttp/psr7": "^1.9 || ^2.4",
142 | "php": "^7.2.5 || ^8.0",
143 | "psr/http-client": "^1.0",
144 | "symfony/deprecation-contracts": "^2.2 || ^3.0"
145 | },
146 | "provide": {
147 | "psr/http-client-implementation": "1.0"
148 | },
149 | "require-dev": {
150 | "bamarni/composer-bin-plugin": "^1.8.1",
151 | "ext-curl": "*",
152 | "php-http/client-integration-tests": "^3.0",
153 | "phpunit/phpunit": "^8.5.29 || ^9.5.23",
154 | "psr/log": "^1.1 || ^2.0 || ^3.0"
155 | },
156 | "suggest": {
157 | "ext-curl": "Required for CURL handler support",
158 | "ext-intl": "Required for Internationalized Domain Name (IDN) support",
159 | "psr/log": "Required for using the Log middleware"
160 | },
161 | "type": "library",
162 | "extra": {
163 | "bamarni-bin": {
164 | "bin-links": true,
165 | "forward-command": false
166 | },
167 | "branch-alias": {
168 | "dev-master": "7.5-dev"
169 | }
170 | },
171 | "autoload": {
172 | "files": [
173 | "src/functions_include.php"
174 | ],
175 | "psr-4": {
176 | "GuzzleHttp\\": "src/"
177 | }
178 | },
179 | "notification-url": "https://packagist.org/downloads/",
180 | "license": [
181 | "MIT"
182 | ],
183 | "authors": [
184 | {
185 | "name": "Graham Campbell",
186 | "email": "hello@gjcampbell.co.uk",
187 | "homepage": "https://github.com/GrahamCampbell"
188 | },
189 | {
190 | "name": "Michael Dowling",
191 | "email": "mtdowling@gmail.com",
192 | "homepage": "https://github.com/mtdowling"
193 | },
194 | {
195 | "name": "Jeremy Lindblom",
196 | "email": "jeremeamia@gmail.com",
197 | "homepage": "https://github.com/jeremeamia"
198 | },
199 | {
200 | "name": "George Mponos",
201 | "email": "gmponos@gmail.com",
202 | "homepage": "https://github.com/gmponos"
203 | },
204 | {
205 | "name": "Tobias Nyholm",
206 | "email": "tobias.nyholm@gmail.com",
207 | "homepage": "https://github.com/Nyholm"
208 | },
209 | {
210 | "name": "Márk Sági-Kazár",
211 | "email": "mark.sagikazar@gmail.com",
212 | "homepage": "https://github.com/sagikazarmark"
213 | },
214 | {
215 | "name": "Tobias Schultze",
216 | "email": "webmaster@tubo-world.de",
217 | "homepage": "https://github.com/Tobion"
218 | }
219 | ],
220 | "description": "Guzzle is a PHP HTTP client library",
221 | "keywords": [
222 | "client",
223 | "curl",
224 | "framework",
225 | "http",
226 | "http client",
227 | "psr-18",
228 | "psr-7",
229 | "rest",
230 | "web service"
231 | ],
232 | "support": {
233 | "issues": "https://github.com/guzzle/guzzle/issues",
234 | "source": "https://github.com/guzzle/guzzle/tree/7.5.0"
235 | },
236 | "funding": [
237 | {
238 | "url": "https://github.com/GrahamCampbell",
239 | "type": "github"
240 | },
241 | {
242 | "url": "https://github.com/Nyholm",
243 | "type": "github"
244 | },
245 | {
246 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
247 | "type": "tidelift"
248 | }
249 | ],
250 | "time": "2022-08-28T15:39:27+00:00"
251 | },
252 | {
253 | "name": "guzzlehttp/promises",
254 | "version": "1.5.2",
255 | "source": {
256 | "type": "git",
257 | "url": "https://github.com/guzzle/promises.git",
258 | "reference": "b94b2807d85443f9719887892882d0329d1e2598"
259 | },
260 | "dist": {
261 | "type": "zip",
262 | "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598",
263 | "reference": "b94b2807d85443f9719887892882d0329d1e2598",
264 | "shasum": ""
265 | },
266 | "require": {
267 | "php": ">=5.5"
268 | },
269 | "require-dev": {
270 | "symfony/phpunit-bridge": "^4.4 || ^5.1"
271 | },
272 | "type": "library",
273 | "extra": {
274 | "branch-alias": {
275 | "dev-master": "1.5-dev"
276 | }
277 | },
278 | "autoload": {
279 | "files": [
280 | "src/functions_include.php"
281 | ],
282 | "psr-4": {
283 | "GuzzleHttp\\Promise\\": "src/"
284 | }
285 | },
286 | "notification-url": "https://packagist.org/downloads/",
287 | "license": [
288 | "MIT"
289 | ],
290 | "authors": [
291 | {
292 | "name": "Graham Campbell",
293 | "email": "hello@gjcampbell.co.uk",
294 | "homepage": "https://github.com/GrahamCampbell"
295 | },
296 | {
297 | "name": "Michael Dowling",
298 | "email": "mtdowling@gmail.com",
299 | "homepage": "https://github.com/mtdowling"
300 | },
301 | {
302 | "name": "Tobias Nyholm",
303 | "email": "tobias.nyholm@gmail.com",
304 | "homepage": "https://github.com/Nyholm"
305 | },
306 | {
307 | "name": "Tobias Schultze",
308 | "email": "webmaster@tubo-world.de",
309 | "homepage": "https://github.com/Tobion"
310 | }
311 | ],
312 | "description": "Guzzle promises library",
313 | "keywords": [
314 | "promise"
315 | ],
316 | "support": {
317 | "issues": "https://github.com/guzzle/promises/issues",
318 | "source": "https://github.com/guzzle/promises/tree/1.5.2"
319 | },
320 | "funding": [
321 | {
322 | "url": "https://github.com/GrahamCampbell",
323 | "type": "github"
324 | },
325 | {
326 | "url": "https://github.com/Nyholm",
327 | "type": "github"
328 | },
329 | {
330 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
331 | "type": "tidelift"
332 | }
333 | ],
334 | "time": "2022-08-28T14:55:35+00:00"
335 | },
336 | {
337 | "name": "guzzlehttp/psr7",
338 | "version": "2.4.4",
339 | "source": {
340 | "type": "git",
341 | "url": "https://github.com/guzzle/psr7.git",
342 | "reference": "3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf"
343 | },
344 | "dist": {
345 | "type": "zip",
346 | "url": "https://api.github.com/repos/guzzle/psr7/zipball/3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf",
347 | "reference": "3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf",
348 | "shasum": ""
349 | },
350 | "require": {
351 | "php": "^7.2.5 || ^8.0",
352 | "psr/http-factory": "^1.0",
353 | "psr/http-message": "^1.0",
354 | "ralouphie/getallheaders": "^3.0"
355 | },
356 | "provide": {
357 | "psr/http-factory-implementation": "1.0",
358 | "psr/http-message-implementation": "1.0"
359 | },
360 | "require-dev": {
361 | "bamarni/composer-bin-plugin": "^1.8.1",
362 | "http-interop/http-factory-tests": "^0.9",
363 | "phpunit/phpunit": "^8.5.29 || ^9.5.23"
364 | },
365 | "suggest": {
366 | "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
367 | },
368 | "type": "library",
369 | "extra": {
370 | "bamarni-bin": {
371 | "bin-links": true,
372 | "forward-command": false
373 | },
374 | "branch-alias": {
375 | "dev-master": "2.4-dev"
376 | }
377 | },
378 | "autoload": {
379 | "psr-4": {
380 | "GuzzleHttp\\Psr7\\": "src/"
381 | }
382 | },
383 | "notification-url": "https://packagist.org/downloads/",
384 | "license": [
385 | "MIT"
386 | ],
387 | "authors": [
388 | {
389 | "name": "Graham Campbell",
390 | "email": "hello@gjcampbell.co.uk",
391 | "homepage": "https://github.com/GrahamCampbell"
392 | },
393 | {
394 | "name": "Michael Dowling",
395 | "email": "mtdowling@gmail.com",
396 | "homepage": "https://github.com/mtdowling"
397 | },
398 | {
399 | "name": "George Mponos",
400 | "email": "gmponos@gmail.com",
401 | "homepage": "https://github.com/gmponos"
402 | },
403 | {
404 | "name": "Tobias Nyholm",
405 | "email": "tobias.nyholm@gmail.com",
406 | "homepage": "https://github.com/Nyholm"
407 | },
408 | {
409 | "name": "Márk Sági-Kazár",
410 | "email": "mark.sagikazar@gmail.com",
411 | "homepage": "https://github.com/sagikazarmark"
412 | },
413 | {
414 | "name": "Tobias Schultze",
415 | "email": "webmaster@tubo-world.de",
416 | "homepage": "https://github.com/Tobion"
417 | },
418 | {
419 | "name": "Márk Sági-Kazár",
420 | "email": "mark.sagikazar@gmail.com",
421 | "homepage": "https://sagikazarmark.hu"
422 | }
423 | ],
424 | "description": "PSR-7 message implementation that also provides common utility methods",
425 | "keywords": [
426 | "http",
427 | "message",
428 | "psr-7",
429 | "request",
430 | "response",
431 | "stream",
432 | "uri",
433 | "url"
434 | ],
435 | "support": {
436 | "issues": "https://github.com/guzzle/psr7/issues",
437 | "source": "https://github.com/guzzle/psr7/tree/2.4.4"
438 | },
439 | "funding": [
440 | {
441 | "url": "https://github.com/GrahamCampbell",
442 | "type": "github"
443 | },
444 | {
445 | "url": "https://github.com/Nyholm",
446 | "type": "github"
447 | },
448 | {
449 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
450 | "type": "tidelift"
451 | }
452 | ],
453 | "time": "2023-03-09T13:19:02+00:00"
454 | },
455 | {
456 | "name": "league/climate",
457 | "version": "3.8.2",
458 | "source": {
459 | "type": "git",
460 | "url": "https://github.com/thephpleague/climate.git",
461 | "reference": "a785a3ac8f584eed4abd45e4e16fe64c46659a28"
462 | },
463 | "dist": {
464 | "type": "zip",
465 | "url": "https://api.github.com/repos/thephpleague/climate/zipball/a785a3ac8f584eed4abd45e4e16fe64c46659a28",
466 | "reference": "a785a3ac8f584eed4abd45e4e16fe64c46659a28",
467 | "shasum": ""
468 | },
469 | "require": {
470 | "php": "^7.3 || ^8.0",
471 | "psr/log": "^1.0 || ^2.0 || ^3.0",
472 | "seld/cli-prompt": "^1.0"
473 | },
474 | "require-dev": {
475 | "mikey179/vfsstream": "^1.6.10",
476 | "mockery/mockery": "^1.4.2",
477 | "phpunit/phpunit": "^9.5.10"
478 | },
479 | "suggest": {
480 | "ext-mbstring": "If ext-mbstring is not available you MUST install symfony/polyfill-mbstring"
481 | },
482 | "type": "library",
483 | "autoload": {
484 | "psr-4": {
485 | "League\\CLImate\\": "src/"
486 | }
487 | },
488 | "notification-url": "https://packagist.org/downloads/",
489 | "license": [
490 | "MIT"
491 | ],
492 | "authors": [
493 | {
494 | "name": "Joe Tannenbaum",
495 | "email": "hey@joe.codes",
496 | "homepage": "http://joe.codes/",
497 | "role": "Developer"
498 | },
499 | {
500 | "name": "Craig Duncan",
501 | "email": "git@duncanc.co.uk",
502 | "homepage": "https://github.com/duncan3dc",
503 | "role": "Developer"
504 | }
505 | ],
506 | "description": "PHP's best friend for the terminal. CLImate allows you to easily output colored text, special formats, and more.",
507 | "keywords": [
508 | "cli",
509 | "colors",
510 | "command",
511 | "php",
512 | "terminal"
513 | ],
514 | "support": {
515 | "issues": "https://github.com/thephpleague/climate/issues",
516 | "source": "https://github.com/thephpleague/climate/tree/3.8.2"
517 | },
518 | "time": "2022-06-18T14:42:08+00:00"
519 | },
520 | {
521 | "name": "psr/http-client",
522 | "version": "1.0.1",
523 | "source": {
524 | "type": "git",
525 | "url": "https://github.com/php-fig/http-client.git",
526 | "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621"
527 | },
528 | "dist": {
529 | "type": "zip",
530 | "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
531 | "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
532 | "shasum": ""
533 | },
534 | "require": {
535 | "php": "^7.0 || ^8.0",
536 | "psr/http-message": "^1.0"
537 | },
538 | "type": "library",
539 | "extra": {
540 | "branch-alias": {
541 | "dev-master": "1.0.x-dev"
542 | }
543 | },
544 | "autoload": {
545 | "psr-4": {
546 | "Psr\\Http\\Client\\": "src/"
547 | }
548 | },
549 | "notification-url": "https://packagist.org/downloads/",
550 | "license": [
551 | "MIT"
552 | ],
553 | "authors": [
554 | {
555 | "name": "PHP-FIG",
556 | "homepage": "http://www.php-fig.org/"
557 | }
558 | ],
559 | "description": "Common interface for HTTP clients",
560 | "homepage": "https://github.com/php-fig/http-client",
561 | "keywords": [
562 | "http",
563 | "http-client",
564 | "psr",
565 | "psr-18"
566 | ],
567 | "support": {
568 | "source": "https://github.com/php-fig/http-client/tree/master"
569 | },
570 | "time": "2020-06-29T06:28:15+00:00"
571 | },
572 | {
573 | "name": "psr/http-factory",
574 | "version": "1.0.1",
575 | "source": {
576 | "type": "git",
577 | "url": "https://github.com/php-fig/http-factory.git",
578 | "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be"
579 | },
580 | "dist": {
581 | "type": "zip",
582 | "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
583 | "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
584 | "shasum": ""
585 | },
586 | "require": {
587 | "php": ">=7.0.0",
588 | "psr/http-message": "^1.0"
589 | },
590 | "type": "library",
591 | "extra": {
592 | "branch-alias": {
593 | "dev-master": "1.0.x-dev"
594 | }
595 | },
596 | "autoload": {
597 | "psr-4": {
598 | "Psr\\Http\\Message\\": "src/"
599 | }
600 | },
601 | "notification-url": "https://packagist.org/downloads/",
602 | "license": [
603 | "MIT"
604 | ],
605 | "authors": [
606 | {
607 | "name": "PHP-FIG",
608 | "homepage": "http://www.php-fig.org/"
609 | }
610 | ],
611 | "description": "Common interfaces for PSR-7 HTTP message factories",
612 | "keywords": [
613 | "factory",
614 | "http",
615 | "message",
616 | "psr",
617 | "psr-17",
618 | "psr-7",
619 | "request",
620 | "response"
621 | ],
622 | "support": {
623 | "source": "https://github.com/php-fig/http-factory/tree/master"
624 | },
625 | "time": "2019-04-30T12:38:16+00:00"
626 | },
627 | {
628 | "name": "psr/http-message",
629 | "version": "1.0.1",
630 | "source": {
631 | "type": "git",
632 | "url": "https://github.com/php-fig/http-message.git",
633 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
634 | },
635 | "dist": {
636 | "type": "zip",
637 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
638 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
639 | "shasum": ""
640 | },
641 | "require": {
642 | "php": ">=5.3.0"
643 | },
644 | "type": "library",
645 | "extra": {
646 | "branch-alias": {
647 | "dev-master": "1.0.x-dev"
648 | }
649 | },
650 | "autoload": {
651 | "psr-4": {
652 | "Psr\\Http\\Message\\": "src/"
653 | }
654 | },
655 | "notification-url": "https://packagist.org/downloads/",
656 | "license": [
657 | "MIT"
658 | ],
659 | "authors": [
660 | {
661 | "name": "PHP-FIG",
662 | "homepage": "http://www.php-fig.org/"
663 | }
664 | ],
665 | "description": "Common interface for HTTP messages",
666 | "homepage": "https://github.com/php-fig/http-message",
667 | "keywords": [
668 | "http",
669 | "http-message",
670 | "psr",
671 | "psr-7",
672 | "request",
673 | "response"
674 | ],
675 | "support": {
676 | "source": "https://github.com/php-fig/http-message/tree/master"
677 | },
678 | "time": "2016-08-06T14:39:51+00:00"
679 | },
680 | {
681 | "name": "psr/log",
682 | "version": "3.0.0",
683 | "source": {
684 | "type": "git",
685 | "url": "https://github.com/php-fig/log.git",
686 | "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001"
687 | },
688 | "dist": {
689 | "type": "zip",
690 | "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001",
691 | "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001",
692 | "shasum": ""
693 | },
694 | "require": {
695 | "php": ">=8.0.0"
696 | },
697 | "type": "library",
698 | "extra": {
699 | "branch-alias": {
700 | "dev-master": "3.x-dev"
701 | }
702 | },
703 | "autoload": {
704 | "psr-4": {
705 | "Psr\\Log\\": "src"
706 | }
707 | },
708 | "notification-url": "https://packagist.org/downloads/",
709 | "license": [
710 | "MIT"
711 | ],
712 | "authors": [
713 | {
714 | "name": "PHP-FIG",
715 | "homepage": "https://www.php-fig.org/"
716 | }
717 | ],
718 | "description": "Common interface for logging libraries",
719 | "homepage": "https://github.com/php-fig/log",
720 | "keywords": [
721 | "log",
722 | "psr",
723 | "psr-3"
724 | ],
725 | "support": {
726 | "source": "https://github.com/php-fig/log/tree/3.0.0"
727 | },
728 | "time": "2021-07-14T16:46:02+00:00"
729 | },
730 | {
731 | "name": "ralouphie/getallheaders",
732 | "version": "3.0.3",
733 | "source": {
734 | "type": "git",
735 | "url": "https://github.com/ralouphie/getallheaders.git",
736 | "reference": "120b605dfeb996808c31b6477290a714d356e822"
737 | },
738 | "dist": {
739 | "type": "zip",
740 | "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
741 | "reference": "120b605dfeb996808c31b6477290a714d356e822",
742 | "shasum": ""
743 | },
744 | "require": {
745 | "php": ">=5.6"
746 | },
747 | "require-dev": {
748 | "php-coveralls/php-coveralls": "^2.1",
749 | "phpunit/phpunit": "^5 || ^6.5"
750 | },
751 | "type": "library",
752 | "autoload": {
753 | "files": [
754 | "src/getallheaders.php"
755 | ]
756 | },
757 | "notification-url": "https://packagist.org/downloads/",
758 | "license": [
759 | "MIT"
760 | ],
761 | "authors": [
762 | {
763 | "name": "Ralph Khattar",
764 | "email": "ralph.khattar@gmail.com"
765 | }
766 | ],
767 | "description": "A polyfill for getallheaders.",
768 | "support": {
769 | "issues": "https://github.com/ralouphie/getallheaders/issues",
770 | "source": "https://github.com/ralouphie/getallheaders/tree/develop"
771 | },
772 | "time": "2019-03-08T08:55:37+00:00"
773 | },
774 | {
775 | "name": "seld/cli-prompt",
776 | "version": "1.0.4",
777 | "source": {
778 | "type": "git",
779 | "url": "https://github.com/Seldaek/cli-prompt.git",
780 | "reference": "b8dfcf02094b8c03b40322c229493bb2884423c5"
781 | },
782 | "dist": {
783 | "type": "zip",
784 | "url": "https://api.github.com/repos/Seldaek/cli-prompt/zipball/b8dfcf02094b8c03b40322c229493bb2884423c5",
785 | "reference": "b8dfcf02094b8c03b40322c229493bb2884423c5",
786 | "shasum": ""
787 | },
788 | "require": {
789 | "php": ">=5.3"
790 | },
791 | "require-dev": {
792 | "phpstan/phpstan": "^0.12.63"
793 | },
794 | "type": "library",
795 | "extra": {
796 | "branch-alias": {
797 | "dev-master": "1.x-dev"
798 | }
799 | },
800 | "autoload": {
801 | "psr-4": {
802 | "Seld\\CliPrompt\\": "src/"
803 | }
804 | },
805 | "notification-url": "https://packagist.org/downloads/",
806 | "license": [
807 | "MIT"
808 | ],
809 | "authors": [
810 | {
811 | "name": "Jordi Boggiano",
812 | "email": "j.boggiano@seld.be"
813 | }
814 | ],
815 | "description": "Allows you to prompt for user input on the command line, and optionally hide the characters they type",
816 | "keywords": [
817 | "cli",
818 | "console",
819 | "hidden",
820 | "input",
821 | "prompt"
822 | ],
823 | "support": {
824 | "issues": "https://github.com/Seldaek/cli-prompt/issues",
825 | "source": "https://github.com/Seldaek/cli-prompt/tree/1.0.4"
826 | },
827 | "time": "2020-12-15T21:32:01+00:00"
828 | },
829 | {
830 | "name": "symfony/deprecation-contracts",
831 | "version": "v3.2.1",
832 | "source": {
833 | "type": "git",
834 | "url": "https://github.com/symfony/deprecation-contracts.git",
835 | "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e"
836 | },
837 | "dist": {
838 | "type": "zip",
839 | "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e",
840 | "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e",
841 | "shasum": ""
842 | },
843 | "require": {
844 | "php": ">=8.1"
845 | },
846 | "type": "library",
847 | "extra": {
848 | "branch-alias": {
849 | "dev-main": "3.3-dev"
850 | },
851 | "thanks": {
852 | "name": "symfony/contracts",
853 | "url": "https://github.com/symfony/contracts"
854 | }
855 | },
856 | "autoload": {
857 | "files": [
858 | "function.php"
859 | ]
860 | },
861 | "notification-url": "https://packagist.org/downloads/",
862 | "license": [
863 | "MIT"
864 | ],
865 | "authors": [
866 | {
867 | "name": "Nicolas Grekas",
868 | "email": "p@tchwork.com"
869 | },
870 | {
871 | "name": "Symfony Community",
872 | "homepage": "https://symfony.com/contributors"
873 | }
874 | ],
875 | "description": "A generic function and convention to trigger deprecation notices",
876 | "homepage": "https://symfony.com",
877 | "support": {
878 | "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.1"
879 | },
880 | "funding": [
881 | {
882 | "url": "https://symfony.com/sponsor",
883 | "type": "custom"
884 | },
885 | {
886 | "url": "https://github.com/fabpot",
887 | "type": "github"
888 | },
889 | {
890 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
891 | "type": "tidelift"
892 | }
893 | ],
894 | "time": "2023-03-01T10:25:55+00:00"
895 | },
896 | {
897 | "name": "symfony/polyfill-mbstring",
898 | "version": "v1.27.0",
899 | "source": {
900 | "type": "git",
901 | "url": "https://github.com/symfony/polyfill-mbstring.git",
902 | "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534"
903 | },
904 | "dist": {
905 | "type": "zip",
906 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
907 | "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
908 | "shasum": ""
909 | },
910 | "require": {
911 | "php": ">=7.1"
912 | },
913 | "provide": {
914 | "ext-mbstring": "*"
915 | },
916 | "suggest": {
917 | "ext-mbstring": "For best performance"
918 | },
919 | "type": "library",
920 | "extra": {
921 | "branch-alias": {
922 | "dev-main": "1.27-dev"
923 | },
924 | "thanks": {
925 | "name": "symfony/polyfill",
926 | "url": "https://github.com/symfony/polyfill"
927 | }
928 | },
929 | "autoload": {
930 | "files": [
931 | "bootstrap.php"
932 | ],
933 | "psr-4": {
934 | "Symfony\\Polyfill\\Mbstring\\": ""
935 | }
936 | },
937 | "notification-url": "https://packagist.org/downloads/",
938 | "license": [
939 | "MIT"
940 | ],
941 | "authors": [
942 | {
943 | "name": "Nicolas Grekas",
944 | "email": "p@tchwork.com"
945 | },
946 | {
947 | "name": "Symfony Community",
948 | "homepage": "https://symfony.com/contributors"
949 | }
950 | ],
951 | "description": "Symfony polyfill for the Mbstring extension",
952 | "homepage": "https://symfony.com",
953 | "keywords": [
954 | "compatibility",
955 | "mbstring",
956 | "polyfill",
957 | "portable",
958 | "shim"
959 | ],
960 | "support": {
961 | "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0"
962 | },
963 | "funding": [
964 | {
965 | "url": "https://symfony.com/sponsor",
966 | "type": "custom"
967 | },
968 | {
969 | "url": "https://github.com/fabpot",
970 | "type": "github"
971 | },
972 | {
973 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
974 | "type": "tidelift"
975 | }
976 | ],
977 | "time": "2022-11-03T14:55:06+00:00"
978 | },
979 | {
980 | "name": "symfony/var-dumper",
981 | "version": "v6.2.7",
982 | "source": {
983 | "type": "git",
984 | "url": "https://github.com/symfony/var-dumper.git",
985 | "reference": "cf8d4ca1ddc1e3cc242375deb8fc23e54f5e2a1e"
986 | },
987 | "dist": {
988 | "type": "zip",
989 | "url": "https://api.github.com/repos/symfony/var-dumper/zipball/cf8d4ca1ddc1e3cc242375deb8fc23e54f5e2a1e",
990 | "reference": "cf8d4ca1ddc1e3cc242375deb8fc23e54f5e2a1e",
991 | "shasum": ""
992 | },
993 | "require": {
994 | "php": ">=8.1",
995 | "symfony/polyfill-mbstring": "~1.0"
996 | },
997 | "conflict": {
998 | "phpunit/phpunit": "<5.4.3",
999 | "symfony/console": "<5.4"
1000 | },
1001 | "require-dev": {
1002 | "ext-iconv": "*",
1003 | "symfony/console": "^5.4|^6.0",
1004 | "symfony/process": "^5.4|^6.0",
1005 | "symfony/uid": "^5.4|^6.0",
1006 | "twig/twig": "^2.13|^3.0.4"
1007 | },
1008 | "suggest": {
1009 | "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
1010 | "ext-intl": "To show region name in time zone dump",
1011 | "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script"
1012 | },
1013 | "bin": [
1014 | "Resources/bin/var-dump-server"
1015 | ],
1016 | "type": "library",
1017 | "autoload": {
1018 | "files": [
1019 | "Resources/functions/dump.php"
1020 | ],
1021 | "psr-4": {
1022 | "Symfony\\Component\\VarDumper\\": ""
1023 | },
1024 | "exclude-from-classmap": [
1025 | "/Tests/"
1026 | ]
1027 | },
1028 | "notification-url": "https://packagist.org/downloads/",
1029 | "license": [
1030 | "MIT"
1031 | ],
1032 | "authors": [
1033 | {
1034 | "name": "Nicolas Grekas",
1035 | "email": "p@tchwork.com"
1036 | },
1037 | {
1038 | "name": "Symfony Community",
1039 | "homepage": "https://symfony.com/contributors"
1040 | }
1041 | ],
1042 | "description": "Provides mechanisms for walking through any arbitrary PHP variable",
1043 | "homepage": "https://symfony.com",
1044 | "keywords": [
1045 | "debug",
1046 | "dump"
1047 | ],
1048 | "support": {
1049 | "source": "https://github.com/symfony/var-dumper/tree/v6.2.7"
1050 | },
1051 | "funding": [
1052 | {
1053 | "url": "https://symfony.com/sponsor",
1054 | "type": "custom"
1055 | },
1056 | {
1057 | "url": "https://github.com/fabpot",
1058 | "type": "github"
1059 | },
1060 | {
1061 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
1062 | "type": "tidelift"
1063 | }
1064 | ],
1065 | "time": "2023-02-24T10:42:00+00:00"
1066 | }
1067 | ],
1068 | "aliases": [],
1069 | "minimum-stability": "stable",
1070 | "stability-flags": [],
1071 | "prefer-stable": false,
1072 | "prefer-lowest": false,
1073 | "platform": {
1074 | "php": ">=8.1"
1075 | },
1076 | "platform-dev": [],
1077 | "plugin-api-version": "2.3.0"
1078 | }
1079 |
--------------------------------------------------------------------------------
/index.php:
--------------------------------------------------------------------------------
1 | [
14 | 'host' => 'localhost:8108',
15 | 'num_typos' => 2,
16 | ],
17 | 'siteMethods' => [
18 | 'typesenseSearch' => function (string $query, int $limit = 30, int $page = 1): SearchResult {
19 | $searchEngine = new TypesenseSearch();
20 | return $searchEngine->search($query, $limit, $page);
21 | }
22 | ],
23 | 'hooks' => [
24 | 'page.update:after' => function (Page $newPage, Page $oldPage) {
25 |
26 | if (TypesenseConfig::isIndexable($newPage)) {
27 | $document = new TypesenseDocument($newPage);
28 | $document
29 | ->setNormalizer(TypesenseConfig::getNormalizer($newPage))
30 | ->upsert();
31 | }
32 | },
33 | 'page.changeTitle:after' => function (Page $newPage) {
34 | if (TypesenseConfig::isIndexable($newPage)) {
35 | $document = new TypesenseDocument($newPage);
36 | $document
37 | ->setNormalizer(TypesenseConfig::getNormalizer($newPage))
38 | ->upsert();
39 | }
40 | },
41 | 'page.delete:after' => function (bool $status, Page $page) {
42 | if ($status) {
43 | $document = new TypesenseDocument($page);
44 | $document->delete();
45 | }
46 | },
47 | 'page.changeTemplate:after' => function (Page $newPage) {
48 |
49 | $document = new TypesenseDocument($newPage);
50 | // if new template is not indexable, remove document
51 | if (!TypesenseConfig::isIndexable($newPage)) {
52 | $document->delete();
53 | } else {
54 | $document
55 | ->setNormalizer(TypesenseConfig::getNormalizer($newPage))
56 | ->upsert();
57 | }
58 |
59 | },
60 | 'page.changeStatus:after' => function (Page $newPage, Page $oldPage) {
61 |
62 | // if new status is published and page is indexable, upsert
63 | if (TypesenseConfig::isIndexable($newPage)) {
64 | $document = new TypesenseDocument($newPage);
65 | $document
66 | ->setNormalizer(TypesenseConfig::getNormalizer($newPage))
67 | ->upsert();
68 | }
69 |
70 | // if new status is unpublished, delete document from typesense index
71 | if ($newPage->isDraft()) {
72 | $document = new TypesenseDocument($newPage);
73 | $document->delete();
74 | }
75 |
76 | }
77 | ],
78 | 'commands' => [
79 | 'typesense:rebuild' => [
80 | 'description' => 'Rebuild typesense index, even if you change fields configuration',
81 | 'args' => [],
82 | 'command' => function (Kirby\CLI\CLI $cli) {
83 | // TODO build command
84 | $cli->success('hello world');
85 |
86 | }
87 | ]
88 | ]
89 | ]);
90 |
--------------------------------------------------------------------------------
/src/SearchResult.php:
--------------------------------------------------------------------------------
1 | items;
21 | }
22 |
23 | public function getTotal(): int
24 | {
25 | return $this->resultsCount;
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/TypesenseClient.php:
--------------------------------------------------------------------------------
1 | apiKey = $apiKey;
23 | $this->host = $host;
24 | }
25 |
26 | public function get(string $endpoint): array
27 | {
28 | return $this->request($endpoint);
29 | }
30 |
31 | public function post(string $endpoint, array $data = []): array
32 | {
33 | return $this->request($endpoint, $data, 'POST');
34 | }
35 |
36 | public function patch(string $endpoint, array $data = []): array
37 | {
38 | return $this->request($endpoint, $data, 'PATCH');
39 | }
40 |
41 | public function delete(string $endpoint, array $data = []): array
42 | {
43 | return $this->request($endpoint, $data, 'DELETE');
44 | }
45 |
46 | public function request(string $endpoint, array $data = [], string $method = 'GET'): array
47 | {
48 |
49 | $requestParams = [
50 | 'headers' => [
51 | 'X-TYPESENSE-API-KEY' => $this->apiKey,
52 | 'Content-Type' => 'application/json'
53 | ],
54 | 'method' => $method,
55 | 'data' => json_encode($data)
56 | ];
57 | $response = Remote::request("{$this->host}/collections/{$endpoint}", $requestParams);
58 |
59 | if (($response->code() >= 200 && $response->code() < 300) || $method === 'DELETE') {
60 | return $response->json();
61 | }
62 |
63 | throw new TypesenseException($response);
64 |
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/src/TypesenseCommand.php:
--------------------------------------------------------------------------------
1 | template()->name(), self::getTemplates()) && $page->isPublished();
31 | }
32 |
33 | public static function getNormalizer(Page $page): Closure
34 | {
35 | return self::getConfig()[$page->template()->name()];
36 | }
37 |
38 | public static function getFields(): array
39 | {
40 | $fields = ['title'];
41 | $config = option('maxchene.typesense.schema.fields');
42 | if (is_array($config)) {
43 | $configFields = A::pluck($config, 'name');
44 | $fields = array_merge($fields, $configFields);
45 | }
46 |
47 | return $fields;
48 |
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/TypesenseDocument.php:
--------------------------------------------------------------------------------
1 | client = new TypesenseClient();
22 | $this->buildSchema();
23 | }
24 |
25 | /**
26 | * Upsert is a shortcut for either update or insert
27 | * Meaning that if a document with current id already exists, document will be updated
28 | * and if not, a new document will be created
29 | *
30 | * Note that id and title fields are mandatory
31 | * and always added to fields you provided in config file
32 | *
33 | * @return void
34 | * @throws \Kirby\Exception\InvalidArgumentException
35 | */
36 | public function upsert(): void
37 | {
38 |
39 | if (empty($this->normalizer)) {
40 | throw new RuntimeException('Missing normalizer closure for Page template ' . $this->page->template()->name());
41 | }
42 |
43 | $data = ($this->normalizer)($this->page);
44 | $data = array_merge($data, [
45 | 'id' => $this->page->uuid()->id(),
46 | 'title' => $this->page->title()->value(),
47 | ]);
48 |
49 | $endpoint = $this->getCollectionName() . '/documents?action=upsert';
50 | try {
51 | $this->client->post($endpoint, $data);
52 | } catch (TypesenseException $exception) {
53 | if ($exception->status === 404 && 'Not Found' === $exception->message) {
54 | $this->createCollection();
55 | $this->client->post($endpoint, $data);
56 | } else {
57 | throw $exception;
58 | }
59 | }
60 | }
61 |
62 | public function setNormalizer(Closure $normalizer): TypesenseDocument
63 | {
64 | $this->normalizer = $normalizer;
65 | return $this;
66 | }
67 |
68 | private function createCollection(): void
69 | {
70 | $this->client->post('', $this->schema);
71 | }
72 |
73 | private function buildSchema(): void
74 | {
75 | $schema = option('maxchene.typesense.schema');
76 | if (empty($schema)) {
77 | throw new RuntimeException('Schema configuration is missing from config file');
78 | }
79 |
80 | if (empty($schema['name'])) {
81 | throw new RuntimeException('Collection name is missing from config file');
82 | }
83 |
84 | $schema['fields'][] = ['name' => 'title', 'type' => 'string'];
85 | $this->schema = $schema;
86 | }
87 |
88 | private function getCollectionName(): string
89 | {
90 | return $this->schema['name'];
91 | }
92 |
93 | /**
94 | * Delete document from Typesense index
95 | * so that it won't show up in search results
96 | *
97 | * @return void
98 | */
99 | public function delete(): void
100 | {
101 | $endpoint = $this->getCollectionName() . '/documents/' . $this->page->uuid()->id();
102 | $this->client->delete($endpoint);
103 | }
104 |
105 | }
106 |
--------------------------------------------------------------------------------
/src/TypesenseException.php:
--------------------------------------------------------------------------------
1 | status = $response->code();
22 | $this->message = json_decode($response->content(), true, 512, JSON_THROW_ON_ERROR)['message'] ?? '';
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/TypesenseItem.php:
--------------------------------------------------------------------------------
1 | html element surrounding keywords",
27 | * }
28 | * ]
29 | */
30 | }
31 |
32 | /**
33 | * Get Kirby page UUID, without page:// prefix
34 | * @return string
35 | */
36 | public function getId(): string
37 | {
38 | return $this->item['document']['id'];
39 | }
40 |
41 | /**
42 | * Get search result title with tag
43 | * @return string
44 | */
45 | public function getTitle(): string
46 | {
47 | return $this->get('title');
48 | }
49 |
50 | /**
51 | * Get Kirby Page from uuid
52 | * @return Page|null
53 | */
54 | public function getPage(): Page|null
55 | {
56 | return App::instance()->site()->index()->findBy('uuid', 'page://' . $this->getId());
57 | }
58 |
59 | public function get(string $field): string
60 | {
61 | foreach ($this->item['highlights'] as $highlight) {
62 | if ($field === $highlight['field']) {
63 | return $highlight['snippet'];
64 | }
65 | }
66 | return '';
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/TypesenseItemInterface.php:
--------------------------------------------------------------------------------
1 | client = new TypesenseClient();
19 | $collection = TypesenseConfig::getCollectionName();
20 | if (empty($collection)) {
21 | throw new RuntimeException('Missing collection name from config file.');
22 | }
23 | $this->searchUrl = "{$collection}/documents/search";
24 | }
25 |
26 | public function search(string $q, int $limit = 30, int $page = 1)
27 | {
28 |
29 | $fields = A::join(TypesenseConfig::getFields(), ',');
30 | $query = new Query([
31 | 'q' => $q,
32 | 'query_by' => $fields,
33 | 'num_typos' => option('maxchene.typesense.num_typos'),
34 | 'per_page' => $limit,
35 | 'page' => $page
36 | ]);
37 | $url = "{$this->searchUrl}{$query->toString(true)}";
38 | ['found' => $resultsCount, 'hits' => $searchItems] = $this->client->get($url);
39 | return new SearchResult(array_map(fn(array $item) => new TypesenseItem($item), $searchItems), $resultsCount);
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------