Passed
Pull Request — main (#3)
by
unknown
10:19 queued 09:11
created

ObjectSearchIndex::findQuery()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 7
nc 2
nop 2
dl 0
loc 11
rs 10
c 1
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace BEdita\ElasticSearch\Model\Index;
5
6
use BEdita\Core\Model\Entity\ObjectEntity;
7
use Cake\Core\Configure;
8
use Cake\Datasource\EntityInterface;
9
use Cake\ElasticSearch\Query;
10
use Cake\ElasticSearch\QueryBuilder;
11
use Cake\I18n\FrozenTime;
12
use Cake\Log\Log;
13
use Cake\ORM\Exception\PersistenceFailedException;
14
use Cake\Validation\Validator;
15
use Elastica\Query\AbstractQuery;
16
use InvalidArgumentException;
17
18
/**
19
 * Base search index for BEdita objects in ElasticSearch.
20
 */
21
class ObjectSearchIndex extends SearchIndex
22
{
23
    /**
24
     * {@inheritDoc}
25
     *
26
     * @codeCoverageIgnore
27
     */
28
    public function validationDefault(Validator $validator): Validator
29
    {
30
        return $validator
31
            ->notEmptyString('uname')
32
            ->requirePresence('uname', 'create')
33
34
            ->notEmptyString('type')
35
            ->requirePresence('type', 'create')
36
37
            ->inList('status', ['on', 'draft', 'off'])
38
            ->requirePresence('status', 'create')
39
40
            ->boolean('deleted')
41
42
            ->dateTime('publish_start')
43
            ->allowEmptyDateTime('publish_start')
44
45
            ->dateTime('publish_end')
46
            ->allowEmptyDateTime('publish_end')
47
48
            ->scalar('title')
49
            ->allowEmptyString('title')
50
51
            ->scalar('description')
52
            ->allowEmptyString('description')
53
54
            ->scalar('body')
55
            ->allowEmptyString('body');
56
    }
57
58
    /**
59
     * @inheritDoc
60
     */
61
    public function reindex(EntityInterface $entity, string $operation): void
62
    {
63
        if (!$entity instanceof ObjectEntity) {
64
            Log::warning(sprintf(
65
                '%s index is supposed to be used only with sub-types of "%s", got "%s" instead',
66
                static::class,
67
                ObjectEntity::class,
68
                get_debug_type($entity),
69
            ));
70
            parent::reindex($entity, $operation);
71
72
            return;
73
        }
74
75
        switch ($operation) {
76
            case 'softDelete':
77
            case 'softDeleteRestore':
78
                $id = (string)$entity->id;
79
                if (!$this->set($id, 'deleted', $entity->deleted)) {
80
                    throw new PersistenceFailedException($this->get($id), ['set']);
81
                }
82
                break;
83
84
            default:
85
                parent::reindex($entity, $operation);
86
        }
87
    }
88
89
    /**
90
     * {@inheritDoc}
91
     *
92
     * @param \Cake\ElasticSearch\Query $query Query object instance.
93
     * @param array{query: string, type?: string|string[]} $options Search options.
94
     * @return \Cake\ElasticSearch\Query
95
     */
96
    public function findQuery(Query $query, array $options): Query
97
    {
98
        if (isset($options['type'])) {
99
            $query = $query->find('type', ['type' => $options['type']]);
100
        }
101
102
        return $query
103
            ->find('available')
104
            ->queryMust(
105
                fn (QueryBuilder $builder): AbstractQuery => $builder
106
                    ->simpleQueryString(['title', 'description', 'body'], $options['query']),
107
            );
108
    }
109
110
    /**
111
     * Find "available" documents, i.e. respective of deletion, status and publication constraints.
112
     *
113
     * @param \Cake\ElasticSearch\Query $query Query object instance.
114
     * @return \Cake\ElasticSearch\Query
115
     */
116
    protected function findAvailable(Query $query): Query
117
    {
118
        return $query->andWhere(function (QueryBuilder $builder): AbstractQuery {
119
            $conditions = [
120
                // Filter objects that are not deleted.
121
                $builder->term('deleted', 'false'),
122
            ];
123
124
            // Filter by object status.
125
            $statusLevel = Configure::read('Status.level', 'all');
126
            if ($statusLevel === 'on') {
127
                $conditions[] = $builder->term('status', 'on');
128
            } elseif ($statusLevel === 'draft') {
129
                $conditions[] = $builder->terms('status', ['on', 'draft']);
130
            }
131
132
            // Filter by publication date.
133
            if ((bool)Configure::read('Publish.checkDate', false)) {
134
                $now = FrozenTime::now();
135
136
                $conditions[] = $builder
137
                    ->or($builder->not($builder->exists('publish_start')), $builder->lte('publish_start', $now))
138
                    ->setMinimumShouldMatch(1);
139
                $conditions[] = $builder
140
                    ->or($builder->not($builder->exists('publish_end')), $builder->gte('publish_end', $now))
141
                    ->setMinimumShouldMatch(1);
142
            }
143
144
            return $builder->and(...$conditions);
145
        });
146
    }
147
148
    /**
149
     * Filter by object type.
150
     *
151
     * @param \Cake\ElasticSearch\Query $query Query object instance.
152
     * @param array{type: string|string[]} $options Finder options.
153
     * @return \Cake\ElasticSearch\Query
154
     */
155
    protected function findType(Query $query, array $options): Query
156
    {
157
        if (empty($options['type'])) {
158
            throw new InvalidArgumentException('Missing or empty `type` option');
159
        }
160
161
        return $query->andWhere(
162
            fn (QueryBuilder $builder): AbstractQuery => $builder->terms('type', (array)$options['type']),
163
        );
164
    }
165
}
166