Passed
Pull Request — main (#3)
by Paolo
01:09
created

ObjectSearchIndex   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 157
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 74
c 1
b 0
f 0
dl 0
loc 157
rs 10
wmc 14

5 Methods

Rating   Name   Duplication   Size   Complexity  
A validationDefault() 0 28 1
A findType() 0 8 2
A findAvailable() 0 29 4
A reindex() 0 25 5
A findQuery() 0 11 2
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
    protected static array $_properties = [
27
        'uname' => ['type' => 'text'],
28
        'type' => ['type' => 'text'],
29
        'status' => ['type' => 'text'],
30
        'deleted' => ['type' => 'boolean'],
31
        'publish_start' => ['type' => 'date'],
32
        'publish_end' => ['type' => 'date'],
33
        'title' => ['type' => 'text'],
34
        'description' => ['type' => 'text'],
35
        'body' => ['type' => 'text'],
36
    ];
37
38
    /**
39
     * {@inheritDoc}
40
     *
41
     * @codeCoverageIgnore
42
     */
43
    public function validationDefault(Validator $validator): Validator
44
    {
45
        return $validator
46
            ->notEmptyString('uname')
47
            ->requirePresence('uname', 'create')
48
49
            ->notEmptyString('type')
50
            ->requirePresence('type', 'create')
51
52
            ->inList('status', ['on', 'draft', 'off'])
53
            ->requirePresence('status', 'create')
54
55
            ->boolean('deleted')
56
57
            ->dateTime('publish_start')
58
            ->allowEmptyDateTime('publish_start')
59
60
            ->dateTime('publish_end')
61
            ->allowEmptyDateTime('publish_end')
62
63
            ->scalar('title')
64
            ->allowEmptyString('title')
65
66
            ->scalar('description')
67
            ->allowEmptyString('description')
68
69
            ->scalar('body')
70
            ->allowEmptyString('body');
71
    }
72
73
    /**
74
     * @inheritDoc
75
     */
76
    public function reindex(EntityInterface $entity, string $operation): void
77
    {
78
        if (!$entity instanceof ObjectEntity) {
79
            Log::warning(sprintf(
80
                '%s index is supposed to be used only with sub-types of "%s", got "%s" instead',
81
                static::class,
82
                ObjectEntity::class,
83
                get_debug_type($entity),
84
            ));
85
            parent::reindex($entity, $operation);
86
87
            return;
88
        }
89
90
        switch ($operation) {
91
            case 'softDelete':
92
            case 'softDeleteRestore':
93
                $id = (string)$entity->id;
94
                if (!$this->set($id, 'deleted', $entity->deleted)) {
95
                    throw new PersistenceFailedException($this->get($id), ['set']);
96
                }
97
                break;
98
99
            default:
100
                parent::reindex($entity, $operation);
101
        }
102
    }
103
104
    /**
105
     * {@inheritDoc}
106
     *
107
     * @param \Cake\ElasticSearch\Query $query Query object instance.
108
     * @param array{query: string, type?: string|string[]} $options Search options.
109
     * @return \Cake\ElasticSearch\Query
110
     */
111
    public function findQuery(Query $query, array $options): Query
112
    {
113
        if (isset($options['type'])) {
114
            $query = $query->find('type', ['type' => $options['type']]);
115
        }
116
117
        return $query
118
            ->find('available')
119
            ->queryMust(
120
                fn (QueryBuilder $builder): AbstractQuery => $builder
121
                    ->simpleQueryString(['title', 'description', 'body'], $options['query']),
122
            );
123
    }
124
125
    /**
126
     * Find "available" documents, i.e. respective of deletion, status and publication constraints.
127
     *
128
     * @param \Cake\ElasticSearch\Query $query Query object instance.
129
     * @return \Cake\ElasticSearch\Query
130
     */
131
    protected function findAvailable(Query $query): Query
132
    {
133
        return $query->andWhere(function (QueryBuilder $builder): AbstractQuery {
134
            $conditions = [
135
                // Filter objects that are not deleted.
136
                $builder->term('deleted', 'false'),
137
            ];
138
139
            // Filter by object status.
140
            $statusLevel = Configure::read('Status.level', 'all');
141
            if ($statusLevel === 'on') {
142
                $conditions[] = $builder->term('status', 'on');
143
            } elseif ($statusLevel === 'draft') {
144
                $conditions[] = $builder->terms('status', ['on', 'draft']);
145
            }
146
147
            // Filter by publication date.
148
            if ((bool)Configure::read('Publish.checkDate', false)) {
149
                $now = FrozenTime::now();
150
151
                $conditions[] = $builder
152
                    ->or($builder->not($builder->exists('publish_start')), $builder->lte('publish_start', $now))
153
                    ->setMinimumShouldMatch(1);
154
                $conditions[] = $builder
155
                    ->or($builder->not($builder->exists('publish_end')), $builder->gte('publish_end', $now))
156
                    ->setMinimumShouldMatch(1);
157
            }
158
159
            return $builder->and(...$conditions);
160
        });
161
    }
162
163
    /**
164
     * Filter by object type.
165
     *
166
     * @param \Cake\ElasticSearch\Query $query Query object instance.
167
     * @param array{type: string|string[]} $options Finder options.
168
     * @return \Cake\ElasticSearch\Query
169
     */
170
    protected function findType(Query $query, array $options): Query
171
    {
172
        if (empty($options['type'])) {
173
            throw new InvalidArgumentException('Missing or empty `type` option');
174
        }
175
176
        return $query->andWhere(
177
            fn (QueryBuilder $builder): AbstractQuery => $builder->terms('type', (array)$options['type']),
178
        );
179
    }
180
}
181