Skip to content

Commit cc51145

Browse files
authored
Merge pull request #6 from texnixe/develop
Version 3.0.0
2 parents 75e76e7 + 52d12ec commit cc51145

File tree

5 files changed

+122
-76
lines changed

5 files changed

+122
-76
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# OS files
22
.DS_Store
3+
.idea
34
*.cache
45

56
# npm modules

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "texnixe/similar",
33
"description": "Find similar pages or files based on similarities between fields",
4-
"version": "2.0.0",
4+
"version": "3.0.0",
55
"type": "kirby-plugin",
66
"license": "MIT",
77
"authors": [
@@ -16,6 +16,7 @@
1616
"kirby3-plugin"
1717
],
1818
"require": {
19+
"php": ">=8.0.0 <8.2.0",
1920
"getkirby/composer-installer": "^1.1"
2021
}
2122
,

index.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
/**
1111
* Kirby 3 Similar Plugin
1212
*
13-
* @version 1.0.3
13+
* @version 3.0.0
1414
* @author Sonja Broda <[email protected]>
1515
* @copyright Sonja Broda <[email protected]>
1616
* @link https://github.com/texnixe/kirby3-similar

lib/Similar.php

Lines changed: 117 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
use Kirby\Cms\Files;
1010
use Kirby\Cms\Page;
1111
use Kirby\Cms\Pages;
12+
use Kirby\Cms\User;
13+
use Kirby\Cms\Users;
1214
use Kirby\Exception\DuplicateException;
15+
use Kirby\Exception\Exception;
1316
use Kirby\Exception\InvalidArgumentException;
1417
use Kirby\Toolkit\A;
1518

@@ -18,68 +21,69 @@ class Similar
1821
/**
1922
* Cache object
2023
*
21-
* @var Cache $cache
24+
* @var Cache|null $cache
2225
*/
23-
protected static $cache;
26+
protected static ?Cache $cache = null;
2427

2528
/**
2629
* Delimiter
2730
*
2831
* @var string
2932
*/
30-
protected $delimiter;
33+
protected string $delimiter;
3134

3235
/**
3336
* Single field as string or multiple fields as array
3437
*
35-
* @var mixed
38+
* @var string|array
3639
*/
37-
protected $fields;
40+
protected string|array $fields;
3841

3942
/**
4043
* Collection to search in
4144
*
42-
* @var Files|Pages
45+
* @var Files|Pages|Users
4346
*/
44-
protected $index;
47+
protected Files|Pages|Users $index;
4548

4649
/**
4750
* Filter results by language
4851
*
4952
* @var bool
5053
*/
51-
protected $languageFilter;
54+
protected bool $languageFilter;
5255

5356
/**
5457
* Threshold for results to count
5558
*
5659
* @var float
5760
*/
58-
protected $threshold;
61+
protected float $threshold;
5962

6063
/**
6164
* Base object
6265
*
63-
* @var File|Page $base File or page object.
66+
* @var File|Page|User $base File or page object.
6467
*/
65-
protected $base;
68+
protected File|Page|User $base;
6669

6770
/**
6871
* Collection type
6972
*
70-
* @var Files|Pages $collection
73+
* @var Files|Pages|Users $collection
7174
*/
72-
protected $collection;
75+
protected Files|Pages|Users $collection;
7376

7477
/**
7578
* User options
7679
*
7780
* @var array
7881
*/
79-
protected $options;
82+
protected array $options;
8083

81-
public function __construct($base, $collection, array $options)
84+
public function __construct(File|Page|User $base, Files|Pages|Users $collection, array $options)
8285
{
86+
8387
$defaults = option('texnixe.similar.defaults');
8488
$defaults['index'] = $base->siblings(false);
8589
$this->options = array_merge($defaults, $options);
@@ -88,7 +92,7 @@ public function __construct($base, $collection, array $options)
8892
$this->delimiter = $this->options['delimiter'];
8993
$this->fields = $this->options['fields'];
9094
$this->index = $this->options['index'];
91-
$this->languageFilter = $this->options['delimiter'];
95+
$this->languageFilter = $this->options['languageFilter'];
9296
$this->threshold = $this->options['threshold'];
9397
}
9498

@@ -116,7 +120,7 @@ public static function flush(): bool
116120
{
117121
try {
118122
return static::cache()->flush();
119-
} catch (InvalidArgumentException $e) {
123+
} catch (InvalidArgumentException) {
120124
return false;
121125
}
122126
}
@@ -125,14 +129,15 @@ public static function flush(): bool
125129
/**
126130
* Returns the similarity index
127131
*
128-
* @param mixed $item
132+
* @param File|Page $item
129133
* @param array $searchItems
130134
*
131135
* @return float
132136
*/
133-
protected function calculateSimilarityIndex($item, array $searchItems): float
137+
protected function calculateSimilarityIndex(File|Page|User $item, array $searchItems): float
134138
{
135139
$indices = [];
140+
136141
foreach ($searchItems as $field => $value) {
137142
$itemFieldValues = $item->{$field}()->split($this->delimiter);
138143
$intersection = count(array_intersect($value[$field], $itemFieldValues));
@@ -141,20 +146,22 @@ protected function calculateSimilarityIndex($item, array $searchItems): float
141146
$indices[] = number_format($intersection / $union * $value['factor'], 5);
142147
}
143148
}
149+
144150
if (($indexCount = count($indices)) !== 0) {
145151
return array_sum($indices) / $indexCount;
146152
}
147153

148-
return (float)0;
154+
return 0.0;
149155
}
150156

151157
/**
152158
* Fetches similar pages
153159
*
154-
* @return Files|Pages
160+
* @return Files|Pages|Users
161+
* @throws Exception
155162
* @throws InvalidArgumentException
156163
*/
157-
public function getData()
164+
public function getData(): Files|Pages|Users
158165
{
159166
// initialize new collection based on type
160167
$similar = $this->collection;
@@ -184,106 +191,95 @@ public function getData()
184191
*
185192
* @return array
186193
* @throws InvalidArgumentException
194+
* @throws Exception
187195
*/
188196
protected function getSearchItems(): array
189197
{
190198
$searchItems = [];
191199
$fields = $this->fields;
192-
if (is_array($fields)) {
193-
if (A::isAssociative($fields)) {
194-
foreach ($fields as $field => $factor) {
195-
if (is_string($field) === false) {
196-
throw new InvalidArgumentException('Field array must be simple array or associative array');
197-
}
198-
// only include fields that have values
199-
$values = $this->base->{$field}()->split($this->delimiter);
200-
if (count($values) > 0) {
201-
$searchItems[$field][$field] = $values;
202-
$searchItems[$field]['factor'] = $factor;
203-
}
204-
}
205-
} else {
206-
foreach ($fields as $field) {
207-
// only include fields that have values
208-
$values = $this->base->{$field}()->split($this->delimiter);
209-
if (count($values) > 0) {
210-
$searchItems[$field][$field] = $values;
211-
$searchItems[$field]['factor'] = 1;
212-
}
213-
}
214-
}
200+
201+
if (!is_string($fields) && !is_array($fields)) {
202+
throw new InvalidArgumentException('Fields must be provided as string or array');
215203
}
204+
216205
if (is_string($fields)) {
217206
$field = $fields;
218207
$searchItems[$field][$field] = $this->base->{$field}()->split($this->delimiter);
219208
$searchItems[$field]['factor'] = 1;
209+
210+
return $searchItems;
220211
}
221-
return $searchItems;
212+
213+
if (A::isAssociative($fields)) {
214+
return $this->searchItemsForAssociativeArray($fields);
215+
}
216+
217+
return $this->searchItemsForIndexArray($fields);
218+
222219
}
223220

224221
/**
225222
* Returns similar pages
226223
*
227-
* @return Files|Pages
224+
* @return Files|Pages|Users
228225
* @throws DuplicateException
226+
* @throws Exception
229227
* @throws InvalidArgumentException
230228
* @throws JsonException
231229
*/
232-
public function getSimilar()
230+
public function getSimilar(): Files|Pages|Users
233231
{
234232
// try to get data from the cache, else create new
235233
if (option('texnixe.similar.cache') === true && $response = static::cache()->get(md5($this->version() . $this->base->id() . json_encode($this->options, JSON_THROW_ON_ERROR)))) {
236234
foreach ($response as $key => $data) {
237235
$this->collection->add($key);
238236
}
239-
$similar = $this->collection;
237+
return $this->collection;
238+
}
239+
240240
// else fetch new data and store in cache
241-
} else {
242-
// make sure we store no old stuff in the cache
243-
if (option('texnixe.similar.cache') === false) {
244-
static::cache()->flush();
245-
}
246-
$similar = $this->getData();
247-
static::cache()->set(
248-
md5($this->version() . $this->base->id() . json_encode($this->options, JSON_THROW_ON_ERROR)),
249-
$similar->toArray(),
250-
option('texnixe.similar.expires')
251-
);
241+
// make sure we store no old stuff in the cache
242+
if (option('texnixe.similar.cache') === false) {
243+
static::cache()->flush();
252244
}
245+
$this->collection = $this->getData();
246+
static::cache()->set(
247+
md5($this->version() . $this->base->id() . json_encode($this->options, JSON_THROW_ON_ERROR)),
248+
$this->collection->toArray(),
249+
option('texnixe.similar.expires')
250+
);
253251

254-
return $similar;
255-
}
252+
return $this->collection;
256253

254+
}
257255

258256
/**
259257
* Filters items by Jaccard Index
260258
*
261259
* @param array $searchItems
262260
*
263-
* @return Files|Pages|\Kirby\Toolkit\Collection
261+
* @return Files|Pages|Users
264262
*/
265-
protected function filterByJaccardIndex(array $searchItems)
263+
protected function filterByJaccardIndex(array $searchItems): Files|Pages|Users
266264
{
267-
return $this->index->map(function ($item) use ($searchItems) {
268-
$item->jaccardIndex = $this->calculateSimilarityIndex($item, $searchItems);
269-
return $item;
270-
})->filterBy('jaccardIndex', '>=', $this->threshold)->sortBy('jaccardIndex', 'desc');
265+
return $this->index
266+
->filter(fn ($item) => $this->calculateSimilarityIndex($item, $searchItems) >= $this->threshold)
267+
->sortBy(fn ($item) => $this->calculateSimilarityIndex($item, $searchItems),'desc');
271268
}
272269

273270
/**
274271
* Filters collection by current language if $languageFilter set to true
275272
*
276273
* @param $similar
277274
*
278-
* @return Files|Pages
275+
* @return Files|Pages|Users
279276
*/
280-
protected function filterByLanguage($similar)
277+
protected function filterByLanguage($similar): Files|Pages|Users
281278
{
282279
if (kirby()->multilang() === true && ($language = kirby()->language())) {
283-
$similar = $similar->filter(function ($item) use ($language) {
284-
return $item->translation($language->code())->exists();
285-
});
280+
$similar = $similar->filter(fn ($item) => $item->translation($language->code())->exists());
286281
}
282+
287283
return $similar;
288284
}
289285

@@ -296,4 +292,52 @@ public function version()
296292
{
297293
return Kirby::plugin('texnixe/similar')->version()[0];
298294
}
295+
296+
/**
297+
* Return seach items for associative array
298+
* @param array $fields
299+
* @return array
300+
* @throws InvalidArgumentException
301+
*/
302+
private function searchItemsForAssociativeArray(array $fields): array
303+
{
304+
$searchItems = [];
305+
306+
foreach ($fields as $field => $factor) {
307+
if (is_string($field) === false) {
308+
throw new InvalidArgumentException('Field array must be simple array or associative array');
309+
}
310+
// only include fields that have values
311+
$values = $this->base->{$field}()->split($this->delimiter);
312+
if (count($values) > 0) {
313+
$searchItems[$field][$field] = $values;
314+
$searchItems[$field]['factor'] = $factor;
315+
}
316+
}
317+
318+
return $searchItems;
319+
}
320+
321+
/**
322+
* Return search items for an indexed array
323+
*
324+
* @param array $fields
325+
* @return array
326+
*/
327+
private function searchItemsForIndexArray(array $fields): array
328+
{
329+
$searchItems = [];
330+
331+
foreach ($fields as $field) {
332+
// only include fields that have values
333+
$values = $this->base->{$field}()->split($this->delimiter);
334+
if (count($values) > 0) {
335+
$searchItems[$field][$field] = $values;
336+
$searchItems[$field]['factor'] = 1;
337+
}
338+
}
339+
340+
return $searchItems;
341+
342+
}
299343
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"description": "Kirby 3 Similar plugin",
44
"author": "Sonja Broda <[email protected]>",
55
"license": "MIT",
6-
"version": "2.0.0",
6+
"version": "3.0.0",
77
"repository": {
88
"type": "git",
99
"url": "https://github.com/texnixe/kirby3-similar"

0 commit comments

Comments
 (0)