ContentRepository::saveDocument()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 2
eloc 5
c 3
b 1
f 0
nc 2
nop 2
dl 0
loc 8
rs 10
1
<?php
2
3
namespace CloudControl\Cms\storage\repository {
4
5
    use CloudControl\Cms\storage\entities\Document;
6
    use CloudControl\Cms\storage\Repository;
7
    use CloudControl\Cms\storage\storage\DocumentStorage;
8
9
    /**
10
     * Created by jensk on 4-9-2017.
11
     */
12
    class ContentRepository
13
    {
14
        protected $storagePath;
15
        protected $contentDbHandle;
16
17
        /**
18
         * ContentRepository constructor.
19
         * @param $storagePath
20
         */
21
        public function __construct($storagePath)
22
        {
23
            $this->storagePath = $storagePath;
24
        }
25
26
        /**
27
         * @return \PDO
28
         */
29
        public function getContentDbHandle()
30
        {
31
            if ($this->contentDbHandle === null) {
32
                $this->contentDbHandle = new \PDO('sqlite:' . $this->storagePath . DIRECTORY_SEPARATOR . 'content.db');
33
            }
34
            return $this->contentDbHandle;
35
        }
36
37
        /**
38
         * Prepare the sql statement
39
         * @param $sql
40
         * @return \PDOStatement
41
         * @throws \Exception
42
         */
43
        protected function getDbStatement($sql)
44
        {
45
            $db = $this->getContentDbHandle();
46
            $stmt = $db->query($sql);
47
            if ($stmt === false) {
48
                $errorInfo = $db->errorInfo();
49
                $errorMsg = $errorInfo[2];
50
                throw new \RuntimeException('SQLite Exception: ' . $errorMsg . ' in SQL: <br /><pre>' . $sql . '</pre>');
51
            }
52
            return $stmt;
53
        }
54
55
        /**
56
         * @param Repository $repository
57
         * @param $document
58
         * @param $db
59
         * @param $documents
60
         * @param $key
61
         * @return mixed
62
         */
63
        private function setAssetsToDocumentFolders(Repository $repository, $document, $db, $documents, $key)
64
        {
65
            if ($this->isFolder($document)) {
66
                $document->dbHandle = $db;
67
                $document->documentStorage = new DocumentStorage($repository);
68
                $documents[$key] = $document;
69
            }
70
71
            return $documents;
72
        }
73
74
        /**
75
         * @param Repository $repository
76
         * @param string $folderPath
77
         * @return array
78
         * @throws \Exception
79
         */
80
        public function getDocumentsWithState(Repository $repository, $folderPath = '/')
81
        {
82
            $db = $this->getContentDbHandle();
83
            $folderPathWithWildcard = $folderPath . '%';
84
85
            $ifRootIndex = 1;
86
            if ($folderPath === '/') {
87
                $ifRootIndex = 0;
88
            }
89
90
            $sql = $this->getSqlForDocumentsWithSate($folderPath, $db, $folderPathWithWildcard, $ifRootIndex);
91
            $stmt = $this->getDbStatement($sql);
92
93
94
            $documents = $stmt->fetchAll(\PDO::FETCH_CLASS, Document::class);
95
            foreach ($documents as $key => $document) {
96
                $documents = $this->setAssetsToDocumentFolders($repository, $document, $db, $documents, $key);
97
            }
98
            return $documents;
99
        }
100
101
        /**
102
         * Get all documents and folders in a certain path
103
         *
104
         * @param Repository $repository
105
         * @param        $folderPath
106
         * @param string $state
107
         * @return array
108
         * @throws \Exception
109
         */
110
        public function getDocumentsByPath(Repository $repository, $folderPath, $state = 'published')
111
        {
112
            if (!in_array($state, Document::$DOCUMENT_STATES, true)) {
113
                throw new \RuntimeException('Unsupported document state: ' . $state);
114
            }
115
            $db = $this->getContentDbHandle();
116
            $folderPathWithWildcard = $folderPath . '%';
117
118
            $sql = 'SELECT *
119
              FROM documents_' . $state . '
120
             WHERE `path` LIKE ' . $db->quote($folderPathWithWildcard) . '
121
               AND instr(substr(`path`, ' . (strlen($folderPath) + 2) . '), \'/\') = 0
122
               AND path != ' . $db->quote($folderPath) . '
123
               AND (publicationDate <= ' . time() . ' OR `type` = "folder")
124
          ORDER BY `type` DESC, `path` ASC';
125
            $stmt = $this->getDbStatement($sql);
126
127
            $documents = $stmt->fetchAll(\PDO::FETCH_CLASS, Document::class);
128
            foreach ($documents as $key => $document) {
129
                $documents = $this->setAssetsToDocumentFolders($repository, $document, $db, $documents, $key);
130
            }
131
            return $documents;
132
        }
133
134
        /**
135
         * @param \CloudControl\Cms\storage\Repository $repository
136
         * @param        $path
137
         * @param string $state
138
         * @return bool|Document
139
         * @throws \Exception
140
         */
141
        public function getDocumentByPath(Repository $repository, $path, $state = 'published')
142
        {
143
            if (!in_array($state, Document::$DOCUMENT_STATES, true)) {
144
                throw new \RuntimeException('Unsupported document state: ' . $state);
145
            }
146
            if ($path === '/') {
147
                return $this->getRootFolder($repository);
148
            }
149
            $db = $this->getContentDbHandle();
150
            $document = $this->fetchDocumentForDocumentByPath($path, $state, $db);
151
            if ($this->isFolder($document)) {
152
                $document->dbHandle = $db;
153
                $document->documentStorage = new DocumentStorage($repository);
154
            }
155
            return $document;
156
        }
157
158
        /**
159
         * Return the result of the query as Document
160
         * @param $sql
161
         * @return mixed
162
         * @throws \Exception
163
         */
164
        protected function fetchDocument($sql)
165
        {
166
            $stmt = $this->getDbStatement($sql);
167
            return $stmt->fetchObject(Document::class);
168
        }
169
170
        /**
171
         * @param Repository $repository
172
         * @param $path
173
         * @return bool|Document
174
         * @throws \Exception
175
         */
176
        public function getDocumentContainerByPath(Repository $repository, $path)
177
        {
178
            $document = $this->getDocumentByPath($repository, $path, 'unpublished');
179
            if ($document === false) {
0 ignored issues
show
introduced by
The condition $document === false is always false.
Loading history...
180
                return false;
181
            }
182
            $slugLength = strlen($document->slug);
183
            $containerPath = substr($path, 0, -$slugLength);
184
            if ($containerPath === '/') {
185
                return $this->getRootFolder($repository);
186
            }
187
            if (substr($containerPath, -1) === '/') {
188
                $containerPath = substr($containerPath, 0, -1);
189
            }
190
            $containerFolder = $this->getDocumentByPath($repository, $containerPath, 'unpublished');
191
            return $containerFolder;
192
        }
193
194
        /**
195
         * Create a (non-existent) root folder Document and return it
196
         * @param Repository $repository
197
         * @return Document
198
         */
199
        protected function getRootFolder(Repository $repository)
200
        {
201
            $rootFolder = new Document();
202
            $rootFolder->path = '/';
203
            $rootFolder->type = 'folder';
204
            $rootFolder->dbHandle = $this->getContentDbHandle();
205
            $rootFolder->documentStorage = new DocumentStorage($repository);
206
            return $rootFolder;
207
        }
208
209
        /**
210
         * Returns the count of all documents stored in the db
211
         *
212
         * @param string $state
213
         *
214
         * @return int
215
         * @throws \Exception
216
         */
217
        public function getTotalDocumentCount($state = 'published')
218
        {
219
            if (!in_array($state, Document::$DOCUMENT_STATES, true)) {
220
                throw new \RuntimeException('Unsupported document state: ' . $state);
221
            }
222
            $db = $this->getContentDbHandle();
223
            $stmt = $db->query('
224
			SELECT count(*)
225
			  FROM documents_' . $state . '
226
			 WHERE `type` != "folder"
227
		');
228
            $result = $stmt->fetch(\PDO::FETCH_ASSOC);
229
            if (!is_array($result)) {
230
                return 0;
231
            }
232
            return (int) current($result);
233
        }
234
235
        /**
236
         * @return array
237
         * @throws \Exception
238
         */
239
        public function getPublishedDocumentsNoFolders()
240
        {
241
            $db = $this->getContentDbHandle();
242
            $sql = '
243
			SELECT *
244
			  FROM documents_published
245
			 WHERE `type` != "folder"
246
		';
247
            $stmt = $db->query($sql);
248
            $result = $stmt->fetchAll(\PDO::FETCH_CLASS, Document::class);
249
            if ($stmt === false || !$stmt->execute()) {
250
                $errorInfo = $db->errorInfo();
251
                $errorMsg = $errorInfo[2];
252
                throw new \RuntimeException('SQLite Exception: ' . $errorMsg . ' in SQL: <br /><pre>' . $sql . '</pre>');
253
            }
254
            return $result;
255
        }
256
257
        /**
258
         * @param $path
259
         * @param int $publicationDate
260
         */
261
        public function publishDocumentByPath($path, $publicationDate = null)
262
        {
263
            $this->publishOrUnpublishDocumentByPath($path, true, $publicationDate);
264
        }
265
266
        /**
267
         * @param $path
268
         * @throws \Exception
269
         */
270
        public function unpublishDocumentByPath($path)
271
        {
272
            $this->publishOrUnpublishDocumentByPath($path, false);
273
        }
274
275
        /**
276
         * @param $path
277
         * @param bool $publish
278
         * @param int $publicationDate
279
         */
280
        public function publishOrUnpublishDocumentByPath($path, $publish = true, $publicationDate = null)
281
        {
282
            $publicationDate = $publicationDate !== null ? intval($publicationDate) : time();
283
            $sql = 'DELETE FROM documents_published
284
					  WHERE `path` = :path';
285
            if ($publish) {
286
                $sql = '
287
				INSERT OR REPLACE INTO documents_published 
288
					  (`id`,`path`,`title`,`slug`,`type`,`documentType`,`documentTypeSlug`,`state`,`lastModificationDate`,`creationDate`,`publicationDate`,`lastModifiedBy`,`fields`,`bricks`,`dynamicBricks`)
289
				SELECT `id`,`path`,`title`,`slug`,`type`,`documentType`,`documentTypeSlug`,"published" AS state,`lastModificationDate`,`creationDate`,' . $publicationDate . ' AS publicationDate, `lastModifiedBy`,`fields`,`bricks`,`dynamicBricks`
290
				  FROM documents_unpublished
291
				 WHERE `path` = :path
292
			';
293
            }
294
            $db = $this->getContentDbHandle();
295
            $stmt = $db->prepare($sql);
296
            if ($stmt === false) {
297
                $errorInfo = $db->errorInfo();
298
                $errorMsg = $errorInfo[2];
299
                throw new \RuntimeException('SQLite Exception: ' . $errorMsg . ' in SQL: <br /><pre>' . $sql . '</pre>');
300
            }
301
            $stmt->bindValue(':path', $path);
302
            $stmt->execute();
303
        }
304
305
        public function cleanPublishedDeletedDocuments()
306
        {
307
            $sql = '   DELETE FROM documents_published
308
						 WHERE documents_published.path IN (
309
						SELECT documents_published.path
310
						  FROM documents_published
311
					 LEFT JOIN documents_unpublished
312
							ON documents_unpublished.path = documents_published.path
313
						 WHERE documents_unpublished.path IS NULL
314
		)';
315
            $stmt = $this->getDbStatement($sql);
316
            $stmt->execute();
317
        }
318
319
        /**
320
         * Save the document to the database
321
         *
322
         * @param Document $documentObject
323
         * @param string $state
324
         *
325
         * @return bool
326
         * @throws \Exception
327
         */
328
        public function saveDocument($documentObject, $state = 'published')
329
        {
330
            if (!in_array($state, Document::$DOCUMENT_STATES, true)) {
331
                throw new \RuntimeException('Unsupported document state: ' . $state);
332
            }
333
            $db = $this->getContentDbHandle();
334
            $stmt = $this->getStatementForSaveDocument($documentObject, $state, $db);
335
            return $stmt->execute();
336
        }
337
338
        /**
339
         * Delete the document from the database
340
         * If it's a folder, also delete it's contents
341
         *
342
         * @param Repository $repository
343
         * @param        $path
344
         * @throws \Exception
345
         */
346
        public function deleteDocumentByPath(Repository $repository, $path)
347
        {
348
            $db = $this->getContentDbHandle();
349
            $documentToDelete = $this->getDocumentByPath($repository, $path, 'unpublished');
350
            if ($documentToDelete instanceof Document) {
0 ignored issues
show
introduced by
$documentToDelete is always a sub-type of CloudControl\Cms\storage\entities\Document.
Loading history...
351
                if ($documentToDelete->type === 'document') {
352
                    $stmt = $this->getDbStatement('
353
                    DELETE FROM documents_unpublished
354
                          WHERE path = ' . $db->quote($path) . '
355
                ');
356
                    $stmt->execute();
357
                } elseif ($documentToDelete->type === 'folder') {
358
                    $folderPathWithWildcard = $path . '%';
359
                    $stmt = $this->getDbStatement('
360
                    DELETE FROM documents_unpublished
361
                          WHERE (path LIKE ' . $db->quote($folderPathWithWildcard) . '
362
                            AND substr(`path`, ' . (strlen($path) + 1) . ', 1) = "/")
363
                            OR path = ' . $db->quote($path) . '
364
                ');
365
                    $stmt->execute();
366
                }
367
            }
368
        }
369
370
        /**
371
         * @param $folderPath
372
         * @param $db
373
         * @param $folderPathWithWildcard
374
         * @param $ifRootIndex
375
         * @return string
376
         */
377
        private function getSqlForDocumentsWithSate($folderPath, $db, $folderPathWithWildcard, $ifRootIndex)
378
        {
379
            $sql = '
380
            SELECT documents_unpublished.*,
381
            	   IFNULL(documents_published.state,"unpublished") AS state,
382
            	   IFNULL(documents_published.publicationDate,NULL) AS publicationDate,
383
            	   (documents_published.lastModificationDate != documents_unpublished.lastModificationDate) AS unpublishedChanges 
384
              FROM documents_unpublished
385
		 LEFT JOIN documents_published
386
         		ON documents_published.path = documents_unpublished.path
387
             WHERE documents_unpublished.`path` LIKE ' . $db->quote($folderPathWithWildcard) . '
388
               AND substr(documents_unpublished.`path`, ' . (strlen($folderPath) + $ifRootIndex + 1) . ') NOT LIKE "%/%"
389
               AND length(documents_unpublished.`path`) > ' . (strlen($folderPath) + $ifRootIndex) . '
390
               AND documents_unpublished.path != ' . $db->quote($folderPath) . '
391
          ORDER BY documents_unpublished.`type` DESC, documents_unpublished.`path` ASC
392
        ';
393
            return $sql;
394
        }
395
396
        /**
397
         * @param $documentObject
398
         * @param $state
399
         * @param $db
400
         * @return \PDOStatement
401
         * @throws \Exception
402
         */
403
        private function getStatementForSaveDocument($documentObject, $state, $db)
404
        {
405
            $stmt = $this->getDbStatement('
406
            INSERT OR REPLACE INTO documents_' . $state . ' (`path`,`title`,`slug`,`type`,`documentType`,`documentTypeSlug`,`state`,`lastModificationDate`,`creationDate`,`lastModifiedBy`,`fields`,`bricks`,`dynamicBricks`)
407
            VALUES(
408
              ' . $db->quote($documentObject->path) . ',
409
              ' . $db->quote($documentObject->title) . ',
410
              ' . $db->quote($documentObject->slug) . ',
411
              ' . $db->quote($documentObject->type) . ',
412
              ' . $db->quote($documentObject->documentType) . ',
413
              ' . $db->quote($documentObject->documentTypeSlug) . ',
414
              ' . $db->quote($documentObject->state) . ',
415
              ' . $db->quote($documentObject->lastModificationDate) . ',
416
              ' . $db->quote($documentObject->creationDate) . ',
417
              ' . $db->quote($documentObject->lastModifiedBy) . ',
418
              ' . $db->quote(json_encode($documentObject->fields)) . ',
419
              ' . $db->quote(json_encode($documentObject->bricks)) . ',
420
              ' . $db->quote(json_encode($documentObject->dynamicBricks)) . '
421
            )
422
        ');
423
            return $stmt;
424
        }
425
426
        /**
427
         * @param Document $document
428
         * @return bool
429
         */
430
        private function isFolder($document)
431
        {
432
            return $document instanceof Document && $document->type === 'folder';
433
        }
434
435
        /**
436
         * @param string $path
437
         * @param string $state
438
         * @param \PDO $db
439
         * @return Document
440
         * @throws \Exception
441
         */
442
        private function fetchDocumentForDocumentByPath($path, $state, $db)
443
        {
444
            $sql = '
445
                SELECT *
446
                  FROM documents_unpublished
447
                 WHERE path = ' . $db->quote($path) . '
448
            ';
449
            if ($state === 'published') {
450
                $sql = '
451
                SELECT *
452
                  FROM documents_published
453
                 WHERE path = ' . $db->quote($path) . '
454
                   AND (`publicationDate` <= ' . time() . ' OR `type` = "folder")
455
            ';
456
            }
457
458
            $document = $this->fetchDocument($sql);
459
            return $document;
460
        }
461
    }
462
463
464
}