Skip to content

Commit

Permalink
Merge branch 'release/4.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
janhenckens committed May 26, 2024
2 parents dad8339 + 89f0444 commit a01e4ad
Show file tree
Hide file tree
Showing 14 changed files with 504 additions and 75 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,23 @@

All notable changes to this project will be documented in this file.

## 4.1.0 - 2024-05-26
### Added
- This release adds support for combining multiple element types into 1 index. Thanks to @andrewmenich for the PR! ([#267](https://github.com/studioespresso/craft-scout/pull/267) && [#69](https://github.com/studioespresso/craft-scout/issues/69), [docs](https://github.com/studioespresso/craft-scout/tree/develop?tab=readme-ov-file#-getelementscallable-queries))

### Fixed
- Don't create an element query right away from ScoutIndex ([#300](https://github.com/studioespresso/craft-scout/pull/300))

## 4.1.0-beta.2 - 2024-05-04
### Fixed
- Added checks for when criteria is an array of element querries ([#297](https://github.com/studioespresso/craft-scout/issues/297))

## 4.1.0-beta.1 - 2024-04-29
### Added
- This release adds support for combining multiple element types into 1 index. Thanks to @andrewmenich for the PR! ([#267](https://github.com/studioespresso/craft-scout/pull/267) && [#69](https://github.com/studioespresso/craft-scout/issues/69))

## 4.0.0 - 2024-03-18
### Changed
- Updated Fractal to 0.20. ([#292](https://github.com/studioespresso/craft-scout/pull/292))

## 3.4.1 - 2024-03-18
Expand Down
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,32 @@ This function accepts an `ElementQuery` and should also return an `ElementQuery`
});
```

---

#### `->getElements(callable $queries)`
This function can be used to query multiple different Element types. It should return an array of ElementQuery objects.

```php
->getElements(function () {
return [
Entry::find()->section('blog'),
Category::find()->group('blogCategories'),
];
});
```

*Note:* When `->getElements()` is used, `->criteria()` and `->elementType()` are ignored. Combining elementTypes also changes the `->transformer()` function - that should be addapted to use `\craft\base\Element $element` as an argument:

```php
->transformer(function (\craft\base\Element $element) {
return [
'title' => $element->title
];
})
```


----
#### `->transformer(callable|string|array|TransformerAbstract $transformer)`
The [transformer](http://fractal.thephpleague.com/transformers/) that should be used to define the data that should be sent to Algolia for each element. If you don’t set this, the default transformer will be used, which includes all of the element’s direct attribute values, but no custom field values.

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "studioespresso/craft-scout",
"description": "Craft Scout provides a simple solution for adding full-text search to your entries. Scout will automatically keep your search indexes in sync with your entries.",
"type": "craft-plugin",
"version": "4.0.0",
"version": "4.1.0",
"keywords": [
"craft",
"cms",
Expand Down
1 change: 0 additions & 1 deletion src/Scout.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,6 @@ function (ElementEvent $event) {
if (!$element->hasMethod('searchable') || !$element->shouldBeSearchable()) {
return;
}

if (Scout::$plugin->getSettings()->queue) {
Craft::$app->getQueue()
->ttr(Scout::$plugin->getSettings()->ttr)
Expand Down
75 changes: 64 additions & 11 deletions src/ScoutIndex.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
use craft\elements\db\ElementQuery;
use craft\elements\Entry;
use Exception;
use Illuminate\Support\Arr;
use League\Fractal\TransformerAbstract;
use yii\base\BaseObject;

/**
* @property-read ElementQuery $criteria
* @property-read array|ElementQuery $criteria
*/
class ScoutIndex extends BaseObject
{
Expand All @@ -24,6 +25,9 @@ class ScoutIndex extends BaseObject
/** @var <class-string> */
public $elementType = Entry::class;

/** @var bool */
public $enforceElementType = true;

/** @var callable|string|array|\League\Fractal\TransformerAbstract */
public $transformer;

Expand All @@ -33,15 +37,15 @@ class ScoutIndex extends BaseObject
/** @var bool */
public $replicaIndex = false;

/** @var callable|ElementQuery */
private $_criteria;
/** @var callable|ElementQuery|ElementQuery[] */
private $criteria;


public function __construct(string $indexName, $config = [])
{
parent::__construct($config);

$this->indexName = $indexName;
$this->_criteria = $this->elementType::find();
}

public static function create(string $indexName): self
Expand All @@ -62,23 +66,72 @@ public function elementType(string $class): self

public function criteria(callable $criteria): self
{
$this->_criteria = $criteria;
$this->criteria = $criteria;

return $this;
}


/**
* @throws Exception
*/
public function getElements(callable $getElements): self
{
$elementQueries = $getElements();

if ($elementQueries instanceof ElementQuery) {
$elementQueries = [$elementQueries];
}

// loop through $elementQueries and check that they are all ElementQuery objects
foreach ($elementQueries as $elementQuery) {
if (!$elementQuery instanceof ElementQuery) {
throw new Exception('You must return a valid ElementQuery or array of ElementQuery objects from the getElements function.');
}

if (is_null($elementQuery->siteId)) {
$elementQuery->siteId = Craft::$app->getSites()->getPrimarySite()->id;
}
}

$this->enforceElementType = false;
$this->criteria = $elementQueries;

return $this;
}

public function getElementType(): string|array
{
if ($this->enforceElementType) {
return $this->elementType;
}

if(is_array($this->criteria)) {
$types = collect($this->criteria)->map(function($criteria){
return Arr::wrap($criteria->elementType);
})->flatten()->unique()->values()->toArray();
return $types;
}
}


/**
* Leverage magic method calling to get the $criteria property, allowing
* lazy calling the Criteria callable.
*
* @return \craft\elements\db\ElementQuery
* @throws \craft\errors\SiteNotFoundException
*/
public function getCriteria(): ElementQuery
public function getCriteria(): ElementQuery|array
{
if (is_callable($this->_criteria)) {
if (!isset($this->criteria)) {
return $this->criteria = $this->elementType::find();
}

if (is_callable($this->criteria)) {

$elementQuery = call_user_func(
$this->_criteria,
$this->criteria,
$this->elementType::find()
);

Expand All @@ -90,10 +143,10 @@ public function getCriteria(): ElementQuery
$elementQuery->siteId = "*";
}

$this->_criteria = $elementQuery;
$this->criteria = $elementQuery;
}

return $this->_criteria;
return $this->criteria;
}

/*
Expand All @@ -114,9 +167,9 @@ public function splitElementsOn(array $splitElementsOn): self
}

/**
* @return callable|\League\Fractal\TransformerAbstract|object
* @throws \yii\base\InvalidConfigException
*
* @return callable|\League\Fractal\TransformerAbstract|object
*/
public function getTransformer()
{
Expand Down
55 changes: 40 additions & 15 deletions src/behaviors/SearchableBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,27 @@
* @mixin Element
*
* @property Element $owner
* @property int $id
* @property int $id
*/
class SearchableBehavior extends Behavior
{
public const EVENT_SHOULD_BE_SEARCHABLE = 'shouldBeSearchableEvent';

public function validatesCriteria(ScoutIndex $scoutIndex): bool
{
if (is_array($scoutIndex->criteria)) {
foreach ($scoutIndex->criteria as $query) {

$criteria = clone $query;
if ($criteria->id($this->owner->id)->exists()) {
return true;
}
continue;
}
return false;

}

$criteria = clone $scoutIndex->criteria;

return $criteria
Expand All @@ -51,20 +64,34 @@ public function getIndices(): Collection
return Scout::$plugin
->getSettings()
->getIndices()
->filter(function(ScoutIndex $scoutIndex) {
$siteIds = array_map(function($siteId) {
return (int) $siteId;
}, Arr::wrap($scoutIndex->criteria->siteId));

return $scoutIndex->elementType === get_class($this->owner)
&& ($scoutIndex->criteria->siteId === '*'
|| in_array((int) $this->owner->siteId, $siteIds));
->filter(function (ScoutIndex $scoutIndex) {
if (is_array($scoutIndex->criteria)) {
$criteriaSiteIds = collect($scoutIndex->criteria)->map(function ($criteria) {
return Arr::wrap($criteria->siteId);
})->flatten()->unique()->values()->toArray();


} else {
$criteriaSiteIds = Arr::wrap($scoutIndex->criteria->siteId);
}

$siteIds = array_map(function ($siteId) {
return (int)$siteId;
}, $criteriaSiteIds);

if (is_array($scoutIndex->criteria)) {
return in_array(get_class($this->owner), $scoutIndex->getElementType())
&& ($criteriaSiteIds[0] === '*' || in_array((int)$this->owner->siteId, $siteIds));
}

return $scoutIndex->getElementType() === get_class($this->owner)
&& ($criteriaSiteIds[0] === '*' || in_array((int)$this->owner->siteId, $siteIds));
});
}

public function searchableUsing(): Collection
{
return $this->getIndices()->map(function(ScoutIndex $scoutIndex) {
return $this->getIndices()->map(function (ScoutIndex $scoutIndex) {
return Scout::$plugin->getSettings()->getEngine($scoutIndex);
});
}
Expand All @@ -75,7 +102,7 @@ public function searchable(bool $propagate = true)
return;
}

$this->searchableUsing()->each(function(Engine $engine) use ($propagate) {
$this->searchableUsing()->each(function (Engine $engine) use ($propagate) {
if (!$this->validatesCriteria($engine->scoutIndex)) {
return $engine->delete($this->owner);
}
Expand All @@ -95,7 +122,6 @@ public function searchable(bool $propagate = true)
} elseif ($propagate) {
$this->searchableRelations();
}

return $engine->update($this->owner);
});
}
Expand Down Expand Up @@ -123,7 +149,7 @@ public function searchableRelations(): void
return;
}

$this->getRelatedElements()->each(function(Element $relatedElement) {
$this->getRelatedElements()->each(function (Element $relatedElement) {
/* @var SearchableBehavior $relatedElement */
$relatedElement->searchable(false);
});
Expand All @@ -139,7 +165,7 @@ public function getRelatedElements(): Collection

if (!empty($settings->relatedElementTypes)) {
return Collection::make($settings->relatedElementTypes)
->flatMap(function($className) {
->flatMap(function ($className) {
return $className::find()->relatedTo($this->owner)->site('*')->all();
});
}
Expand Down Expand Up @@ -199,7 +225,6 @@ public function shouldBeSearchable(): bool

return $event->shouldBeSearchable;
}

return true;
}
}
45 changes: 35 additions & 10 deletions src/console/controllers/scout/IndexController.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,41 @@ public function actionImport($index = '')
]));
$this->stdout("Added ImportIndex job for '{$engine->scoutIndex->indexName}' to the queue".PHP_EOL, Console::FG_GREEN);
} else {
$totalElements = $engine->scoutIndex->criteria->count();
$elementsUpdated = 0;
$batch = $engine->scoutIndex->criteria->batch(
Scout::$plugin->getSettings()->batch_size
);

foreach ($batch as $elements) {
$engine->update($elements);
$elementsUpdated += count($elements);
$this->stdout("Updated {$elementsUpdated}/{$totalElements} element(s) in {$engine->scoutIndex->indexName}\n", Console::FG_GREEN);
// check if $engine->scoutIndex->criteria is iterable
if (is_array($engine->scoutIndex->criteria)) {
// use array_reduce to get the count of elements
$totalElements = array_reduce($engine->scoutIndex->criteria, function ($carry, $query) {
return $carry + $query->count();
}, 0);



foreach($engine->scoutIndex->criteria as $query) {
$elementsUpdated = 0;
$batch = $query->batch(
Scout::$plugin->getSettings()->batch_size
);

foreach ($batch as $elements) {
$engine->update($elements);
$elementsUpdated += count($elements);
$this->stdout("Updated {$elementsUpdated}/{$totalElements} element(s) in {$engine->scoutIndex->indexName} via " . get_class($query) . "\n", Console::FG_GREEN);
}
}

} else {
$totalElements = $engine->scoutIndex->criteria->count();

$elementsUpdated = 0;
$batch = $engine->scoutIndex->criteria->batch(
Scout::$plugin->getSettings()->batch_size
);

foreach ($batch as $elements) {
$engine->update($elements);
$elementsUpdated += count($elements);
$this->stdout("Updated {$elementsUpdated}/{$totalElements} element(s) in {$engine->scoutIndex->indexName}\n", Console::FG_GREEN);
}
}
}
});
Expand Down
Loading

0 comments on commit a01e4ad

Please sign in to comment.