Completed
Push — main ( 0d3bab...165365 )
by Dante
14s queued 12s
created

CollectionHandler::documentToAdd()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 5
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 11
rs 9.6111
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * Chatlas BEdita plugin
6
 *
7
 * Copyright 2023 Atlas Srl
8
 */
9
namespace BEdita\Chatlas\Index;
10
11
use BEdita\Chatlas\Client\ChatlasClient;
12
use BEdita\Core\Filesystem\FilesystemRegistry;
13
use BEdita\Core\Model\Entity\ObjectEntity;
14
use Cake\Http\Client\FormData;
15
use Cake\Log\LogTrait;
16
use Cake\Utility\Hash;
17
use Laminas\Diactoros\UploadedFile;
18
19
/**
20
 * Handle Chatlas collection via API.
21
 */
22
class CollectionHandler
23
{
24
    use LogTrait;
25
26
    /**
27
     * Chatlas API client
28
     *
29
     * @var \BEdita\Chatlas\Client\ChatlasClient
30
     */
31
    protected ChatlasClient $chatlas;
32
33
    /**
34
     * List of properties to exclude when saving Chatlas collection metadata
35
     *
36
     * @var array
37
     */
38
    public const COLLECTION_FIELDS_EXCLUDED = [
39
        'uname',
40
        'type',
41
        'created',
42
        'modified',
43
        'locked',
44
        'published',
45
        'created_by',
46
        'modified_by',
47
        'collection_uuid',
48
        'collection_updated',
49
    ];
50
51
    /**
52
     * List of properties to check when updating a Chatlas collection index
53
     *
54
     * @var array
55
     */
56
    public const DOCUMENT_PROPERTIES = [
57
        'title',
58
        'description',
59
        'body',
60
    ];
61
62
    /**
63
     * Handler constructor
64
     */
65
    public function __construct()
66
    {
67
        $this->chatlas = new ChatlasClient();
68
    }
69
70
    /**
71
     * Create Chatlas collection
72
     *
73
     * @param \BEdita\Core\Model\Entity\ObjectEntity $collection Collection entity
74
     * @return void
75
     */
76
    public function createCollection(ObjectEntity $collection): void
77
    {
78
        $msg = sprintf('Creating collection "%s"', $collection->get('title'));
79
        $this->log($msg, 'info');
80
81
        $response = $this->chatlas->post('/collections', $this->chatlasCollection($collection));
82
        $collection->set('collection_uuid', Hash::get($response->getJson(), 'uuid'));
83
        $collection->set('collection_updated', date('c'));
84
        $collection->getTable()->saveOrFail($collection, ['_skipAfterSave' => true]);
85
    }
86
87
    /**
88
     * Update Chatlas collection
89
     *
90
     * @param \BEdita\Core\Model\Entity\ObjectEntity $collection Collection entity
91
     * @return void
92
     */
93
    public function updateCollection(ObjectEntity $collection): void
94
    {
95
        $msg = sprintf('Updating collection "%s"', $collection->get('title'));
96
        $this->log($msg, 'info');
97
        $path = sprintf('/collections/%s', $collection->get('collection_uuid'));
98
        $this->chatlas->patch($path, $this->chatlasCollection($collection));
99
        $collection->set('collection_updated', date('c'));
100
        $collection->getTable()->saveOrFail($collection, ['_skipAfterSave' => true]);
101
    }
102
103
    /**
104
     * Fetch Chatlas collection fields
105
     *
106
     * @param \BEdita\Core\Model\Entity\ObjectEntity $collection Collection entity
107
     * @return array
108
     */
109
    protected function chatlasCollection(ObjectEntity $collection): array
110
    {
111
        $fields = array_diff_key(
112
            $collection->toArray(),
113
            array_flip(static::COLLECTION_FIELDS_EXCLUDED)
114
        ) + ['deleted' => $collection->get('deleted')];
115
116
        return [
117
            'name' => $collection->get('uname'),
118
            'cmetadata' => array_filter($fields),
119
        ];
120
    }
121
122
    /**
123
     * Remove Chatlas collection
124
     *
125
     * @param \BEdita\Core\Model\Entity\ObjectEntity $collection Collection entity
126
     * @return void
127
     */
128
    public function removeCollection(ObjectEntity $collection): void
129
    {
130
        $msg = sprintf('Removing collection "%s"', $collection->get('title'));
131
        $this->log($msg, 'info');
132
        $path = sprintf('/collections/%s', $collection->get('collection_uuid'));
133
        $this->chatlas->delete($path);
134
        $collection->set('collection_uuid', null);
135
    }
136
137
    /**
138
     * Add document to collection index
139
     *
140
     * @param \BEdita\Core\Model\Entity\ObjectEntity $collection Collection entity
141
     * @param \BEdita\Core\Model\Entity\ObjectEntity $entity Document entity
142
     * @return void
143
     */
144
    protected function addDocument(ObjectEntity $collection, ObjectEntity $entity): void
145
    {
146
        if ($entity->get('status') !== 'on' || $entity->get('deleted')) {
147
            $msg = sprintf('Skipping doc "%s" - ', $entity->get('title')) .
148
                sprintf('status "%s" - deleted %b', $entity->get('status'), (bool)$entity->get('deleted'));
149
            $this->log($msg, 'info');
150
151
            return;
152
        }
153
        if ($entity->get('type') === 'files') {
154
            $this->uploadDocument($collection, $entity);
155
156
            return;
157
        }
158
        $content = sprintf("%s\n%s", (string)$entity->get('title'), strip_tags((string)$entity->get('body')));
159
        $body = [
160
            'content' => $content,
161
            'collection_id' => $collection->get('collection_uuid'),
162
            'document_id' => $entity->get('id'),
163
            'metadata' => ['type' => $entity->get('type')],
164
        ];
165
        $this->chatlas->post('/index', $body);
166
        $entity->set('index_updated', date('c'));
167
        $entity->getTable()->saveOrFail($entity, ['_skipAfterSave' => true]);
168
    }
169
170
    /**
171
     * Upload file to index
172
     *
173
     * @param \BEdita\Core\Model\Entity\ObjectEntity $collection Collection entity
174
     * @param \BEdita\Core\Model\Entity\ObjectEntity $entity Document entity
175
     * @return void
176
     */
177
    protected function uploadDocument(ObjectEntity $collection, ObjectEntity $entity): void
178
    {
179
        $form = new FormData();
180
        if (empty($entity->get('streams'))) {
181
            $entity->getTable()->loadInto($entity, ['Streams']);
182
        }
183
        /** @var \BEdita\Core\Model\Entity\Stream|null $stream */
184
        $stream = Hash::get($entity, 'streams.0');
185
        if (empty($stream)) {
186
            return;
187
        }
188
        $resource = FilesystemRegistry::getMountManager()->readStream($stream->uri);
189
        $file = new UploadedFile(
190
            $resource,
191
            $stream->file_size,
192
            UPLOAD_ERR_OK,
193
            $stream->file_name,
194
            $stream->mime_type,
195
        );
196
        $form->addFile('file', $file);
197
        $form->addMany([
198
            'collection_id' => $collection->get('collection_uuid'),
199
            'document_id' => $entity->get('id'),
200
            'metadata' => json_encode([
201
                'type' => $entity->get('type'),
202
                'file' => $stream->file_name,
203
            ]),
204
        ]);
205
        $this->chatlas->postMultipart(
206
            '/index/upload',
207
            $form
208
        );
209
        $entity->set('index_updated', date('c'));
210
        $entity->getTable()->saveOrFail($entity, ['_skipAfterSave' => true]);
211
    }
212
213
    /**
214
     * Update collection index for a document
215
     *
216
     * @param \BEdita\Core\Model\Entity\ObjectEntity $collection Collection entity
217
     * @param \BEdita\Core\Model\Entity\ObjectEntity $entity Document entity
218
     * @param bool $forceAdd Force add document action
219
     * @return void
220
     */
221
    public function updateDocument(ObjectEntity $collection, ObjectEntity $entity, bool $forceAdd = false): void
222
    {
223
        if (
224
            $entity->isNew() ||
225
            ($entity->isDirty('deleted') && !$entity->get('deleted')) ||
226
            ($entity->isDirty('status') && $entity->get('status') === 'on') ||
227
            $forceAdd
228
        ) {
229
            $this->log($this->logMessage('Add', $collection, $entity), 'info');
230
            $this->addDocument($collection, $entity);
231
232
            return;
233
        }
234
        if (
235
            ($entity->isDirty('deleted') && $entity->get('deleted')) ||
236
            ($entity->isDirty('status') && in_array($entity->get('status'), ['draft', 'off']))
237
        ) {
238
            $this->removeDocument($collection, $entity);
239
240
            return;
241
        }
242
        // see if some object properties have changed (no effect on `files` objects)
243
        if ($entity->get('type') === 'files') {
244
            return;
245
        }
246
247
        foreach (static::DOCUMENT_PROPERTIES as $field) {
248
            if ($entity->isDirty($field)) {
249
                $this->log($this->logMessage('Update', $collection, $entity), 'info');
250
                $this->addDocument($collection, $entity);
251
252
                return;
253
            }
254
        }
255
    }
256
257
    /**
258
     * Log message on index action
259
     *
260
     * @param string $action Action to log
261
     * @param \BEdita\Core\Model\Entity\ObjectEntity $collection Collection entity
262
     * @param \BEdita\Core\Model\Entity\ObjectEntity $entity Document entity
263
     * @return string
264
     */
265
    protected function logMessage(string $action, ObjectEntity $collection, ObjectEntity $entity): string
266
    {
267
        return sprintf('%s document "%s"', $action, $entity->get('title')) .
268
            sprintf(' [collection "%s"]', $collection->get('title'));
269
    }
270
271
    /**
272
     * Remove document from collection index
273
     *
274
     * @param \BEdita\Core\Model\Entity\ObjectEntity $collection Collection entity
275
     * @param \BEdita\Core\Model\Entity\ObjectEntity $entity Document entity
276
     * @return void
277
     */
278
    public function removeDocument(ObjectEntity $collection, ObjectEntity $entity): void
279
    {
280
        $this->log($this->logMessage('Remove', $collection, $entity), 'info');
281
        $path = sprintf('/index/%s/%s', $collection->get('collection_uuid'), $entity->get('id'));
282
        $this->chatlas->delete($path);
283
        $entity->set('index_updated', null);
284
        $entity->getTable()->saveOrFail($entity, ['_skipAfterSave' => true]);
285
    }
286
}
287