Passed
Push — develop ( 57c6aa...996da2 )
by Jens
03:16
created

ContentRepository::isFolder()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 1
nc 2
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
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 substr(`path`, ' . (strlen($folderPath) + 1) . ') NOT LIKE "%/%"
122
               AND path != ' . $db->quote($folderPath) . '
123
          ORDER BY `type` DESC, `path` ASC';
124
            $stmt = $this->getDbStatement($sql);
125
126
            $documents = $stmt->fetchAll(\PDO::FETCH_CLASS, Document::class);
127
            foreach ($documents as $key => $document) {
128
                $documents = $this->setAssetsToDocumentFolders($repository, $document, $db, $documents, $key);
129
            }
130
            return $documents;
131
        }
132
133
        /**
134
         * @param \CloudControl\Cms\storage\Repository $repository
135
         * @param        $path
136
         * @param string $state
137
         * @return bool|Document
138
         * @throws \Exception
139
         */
140
        public function getDocumentByPath(Repository $repository, $path, $state = 'published')
141
        {
142
            if (!in_array($state, Document::$DOCUMENT_STATES, true)) {
143
                throw new \RuntimeException('Unsupported document state: ' . $state);
144
            }
145
            if ($path === '/') {
146
                return $this->getRootFolder($repository);
147
            }
148
            $db = $this->getContentDbHandle();
149
            $document = $this->fetchDocumentForDocumentByPath($path, $state, $db);
150
            if ($this->isFolder($document)) {
151
                $document->dbHandle = $db;
152
                $document->documentStorage = new DocumentStorage($repository);
153
            }
154
            return $document;
155
        }
156
157
        /**
158
         * Return the result of the query as Document
159
         * @param $sql
160
         * @return mixed
161
         * @throws \Exception
162
         */
163
        protected function fetchDocument($sql)
164
        {
165
            $stmt = $this->getDbStatement($sql);
166
            return $stmt->fetchObject(Document::class);
167
        }
168
169
        /**
170
         * @param Repository $repository
171
         * @param $path
172
         * @return bool|Document
173
         * @throws \Exception
174
         */
175
        public function getDocumentContainerByPath(Repository $repository, $path)
176
        {
177
            $document = $this->getDocumentByPath($repository, $path, 'unpublished');
178
            if ($document === false) {
179
                return false;
180
            }
181
            $slugLength = strlen($document->slug);
182
            $containerPath = substr($path, 0, -$slugLength);
183
            if ($containerPath === '/') {
184
                return $this->getRootFolder($repository);
185
            }
186
            if (substr($containerPath, -1) === '/') {
187
                $containerPath = substr($containerPath, 0, -1);
188
            }
189
            $containerFolder = $this->getDocumentByPath($repository, $containerPath, 'unpublished');
190
            return $containerFolder;
191
        }
192
193
        /**
194
         * Create a (non-existent) root folder Document and return it
195
         * @param Repository $repository
196
         * @return Document
197
         */
198
        protected function getRootFolder(Repository $repository)
199
        {
200
            $rootFolder = new Document();
201
            $rootFolder->path = '/';
202
            $rootFolder->type = 'folder';
203
            $rootFolder->dbHandle = $this->getContentDbHandle();
204
            $rootFolder->documentStorage = new DocumentStorage($repository);
205
            return $rootFolder;
206
        }
207
208
        /**
209
         * Returns the count of all documents stored in the db
210
         *
211
         * @param string $state
212
         *
213
         * @return int
214
         * @throws \Exception
215
         */
216
        public function getTotalDocumentCount($state = 'published')
217
        {
218
            if (!in_array($state, Document::$DOCUMENT_STATES, true)) {
219
                throw new \RuntimeException('Unsupported document state: ' . $state);
220
            }
221
            $db = $this->getContentDbHandle();
222
            $stmt = $db->query('
223
			SELECT count(*)
224
			  FROM documents_' . $state . '
225
			 WHERE `type` != "folder"
226
		');
227
            $result = $stmt->fetch(\PDO::FETCH_ASSOC);
228
            if (!is_array($result)) {
229
                return 0;
230
            }
231
            return (int)current($result);
232
        }
233
234
        /**
235
         * @return array
236
         * @throws \Exception
237
         */
238
        public function getPublishedDocumentsNoFolders()
239
        {
240
            $db = $this->getContentDbHandle();
241
            $sql = '
242
			SELECT *
243
			  FROM documents_published
244
			 WHERE `type` != "folder"
245
		';
246
            $stmt = $db->query($sql);
247
            $result = $stmt->fetchAll(\PDO::FETCH_CLASS, Document::class);
248
            if ($stmt === false || !$stmt->execute()) {
249
                $errorInfo = $db->errorInfo();
250
                $errorMsg = $errorInfo[2];
251
                throw new \RuntimeException('SQLite Exception: ' . $errorMsg . ' in SQL: <br /><pre>' . $sql . '</pre>');
252
            }
253
            return $result;
254
        }
255
256
        /**
257
         * @param $path
258
         * @throws \Exception
259
         */
260
        public function publishDocumentByPath($path)
261
        {
262
            $this->publishOrUnpublishDocumentByPath($path);
263
        }
264
265
        /**
266
         * @param $path
267
         * @throws \Exception
268
         */
269
        public function unpublishDocumentByPath($path)
270
        {
271
            $this->publishOrUnpublishDocumentByPath($path, false);
272
        }
273
274
        /**
275
         * @param $path
276
         * @param bool $publish
277
         * @throws \Exception
278
         */
279
        public function publishOrUnpublishDocumentByPath($path, $publish = true)
280
        {
281
            $sql = 'DELETE FROM documents_published
282
					  WHERE `path` = :path';
283
            if ($publish) {
284
                $sql = '
285
				INSERT OR REPLACE INTO documents_published 
286
					  (`id`,`path`,`title`,`slug`,`type`,`documentType`,`documentTypeSlug`,`state`,`lastModificationDate`,`creationDate`,`publicationDate`,`lastModifiedBy`,`fields`,`bricks`,`dynamicBricks`)
287
				SELECT `id`,`path`,`title`,`slug`,`type`,`documentType`,`documentTypeSlug`,"published" AS state,`lastModificationDate`,`creationDate`,' . time() . ' AS publicationDate, `lastModifiedBy`,`fields`,`bricks`,`dynamicBricks`
288
				  FROM documents_unpublished
289
				 WHERE `path` = :path
290
			';
291
            }
292
            $db = $this->getContentDbHandle();
293
            $stmt = $db->prepare($sql);
294
            if ($stmt === false) {
295
                $errorInfo = $db->errorInfo();
296
                $errorMsg = $errorInfo[2];
297
                throw new \RuntimeException('SQLite Exception: ' . $errorMsg . ' in SQL: <br /><pre>' . $sql . '</pre>');
298
            }
299
            $stmt->bindValue(':path', $path);
300
            $stmt->execute();
301
        }
302
303
        public function cleanPublishedDeletedDocuments()
304
        {
305
            $sql = '   DELETE FROM documents_published
306
						 WHERE documents_published.path IN (
307
						SELECT documents_published.path
308
						  FROM documents_published
309
					 LEFT JOIN documents_unpublished
310
							ON documents_unpublished.path = documents_published.path
311
						 WHERE documents_unpublished.path IS NULL
312
		)';
313
            $stmt = $this->getDbStatement($sql);
314
            $stmt->execute();
315
        }
316
317
        /**
318
         * Save the document to the database
319
         *
320
         * @param Document $documentObject
321
         * @param string $state
322
         *
323
         * @return bool
324
         * @throws \Exception
325
         */
326
        public function saveDocument($documentObject, $state = 'published')
327
        {
328
            if (!in_array($state, Document::$DOCUMENT_STATES, true)) {
329
                throw new \RuntimeException('Unsupported document state: ' . $state);
330
            }
331
            $db = $this->getContentDbHandle();
332
            $stmt = $this->getStatementForSaveDocument($documentObject, $state, $db);
333
            return $stmt->execute();
334
        }
335
336
        /**
337
         * Delete the document from the database
338
         * If it's a folder, also delete it's contents
339
         *
340
         * @param Repository $repository
341
         * @param        $path
342
         * @throws \Exception
343
         */
344
        public function deleteDocumentByPath(Repository $repository, $path)
345
        {
346
            $db = $this->getContentDbHandle();
347
            $documentToDelete = $this->getDocumentByPath($repository, $path, 'unpublished');
348
            if ($documentToDelete instanceof Document) {
349
                if ($documentToDelete->type === 'document') {
350
                    $stmt = $this->getDbStatement('
351
                    DELETE FROM documents_unpublished
352
                          WHERE path = ' . $db->quote($path) . '
353
                ');
354
                    $stmt->execute();
355
                } elseif ($documentToDelete->type === 'folder') {
356
                    $folderPathWithWildcard = $path . '%';
357
                    $stmt = $this->getDbStatement('
358
                    DELETE FROM documents_unpublished
359
                          WHERE (path LIKE ' . $db->quote($folderPathWithWildcard) . '
360
                            AND substr(`path`, ' . (strlen($path) + 1) . ', 1) = "/")
361
                            OR path = ' . $db->quote($path) . '
362
                ');
363
                    $stmt->execute();
364
                }
365
            }
366
        }
367
368
        /**
369
         * @param $folderPath
370
         * @param $db
371
         * @param $folderPathWithWildcard
372
         * @param $ifRootIndex
373
         * @return string
374
         */
375
        private function getSqlForDocumentsWithSate($folderPath, $db, $folderPathWithWildcard, $ifRootIndex)
376
        {
377
            $sql = '
378
            SELECT documents_unpublished.*,
379
            	   IFNULL(documents_published.state,"unpublished") AS state,
380
            	   IFNULL(documents_published.publicationDate,NULL) AS publicationDate,
381
            	   (documents_published.lastModificationDate != documents_unpublished.lastModificationDate) AS unpublishedChanges 
382
              FROM documents_unpublished
383
		 LEFT JOIN documents_published
384
         		ON documents_published.path = documents_unpublished.path
385
             WHERE documents_unpublished.`path` LIKE ' . $db->quote($folderPathWithWildcard) . '
386
               AND substr(documents_unpublished.`path`, ' . (strlen($folderPath) + $ifRootIndex + 1) . ') NOT LIKE "%/%"
387
               AND length(documents_unpublished.`path`) > ' . (strlen($folderPath) + $ifRootIndex) . '
388
               AND documents_unpublished.path != ' . $db->quote($folderPath) . '
389
          ORDER BY documents_unpublished.`type` DESC, documents_unpublished.`path` ASC
390
        ';
391
            return $sql;
392
        }
393
394
        /**
395
         * @param $documentObject
396
         * @param $state
397
         * @param $db
398
         * @return \PDOStatement
399
         * @throws \Exception
400
         */
401
        private function getStatementForSaveDocument($documentObject, $state, $db)
402
        {
403
            $stmt = $this->getDbStatement('
404
            INSERT OR REPLACE INTO documents_' . $state . ' (`path`,`title`,`slug`,`type`,`documentType`,`documentTypeSlug`,`state`,`lastModificationDate`,`creationDate`,`lastModifiedBy`,`fields`,`bricks`,`dynamicBricks`)
405
            VALUES(
406
              ' . $db->quote($documentObject->path) . ',
407
              ' . $db->quote($documentObject->title) . ',
408
              ' . $db->quote($documentObject->slug) . ',
409
              ' . $db->quote($documentObject->type) . ',
410
              ' . $db->quote($documentObject->documentType) . ',
411
              ' . $db->quote($documentObject->documentTypeSlug) . ',
412
              ' . $db->quote($documentObject->state) . ',
413
              ' . $db->quote($documentObject->lastModificationDate) . ',
414
              ' . $db->quote($documentObject->creationDate) . ',
415
              ' . $db->quote($documentObject->lastModifiedBy) . ',
416
              ' . $db->quote(json_encode($documentObject->fields)) . ',
417
              ' . $db->quote(json_encode($documentObject->bricks)) . ',
418
              ' . $db->quote(json_encode($documentObject->dynamicBricks)) . '
419
            )
420
        ');
421
            return $stmt;
422
        }
423
424
        /**
425
         * @param Document $document
426
         * @return bool
427
         */
428
        private function isFolder($document)
429
        {
430
            return $document instanceof Document && $document->type === 'folder';
431
        }
432
433
        /**
434
         * @param $path
435
         * @param $state
436
         * @param $db
437
         * @return mixed
438
         * @throws \Exception
439
         */
440
        private function fetchDocumentForDocumentByPath($path, $state, $db)
441
        {
442
            $document = $this->fetchDocument('
443
            SELECT *
444
              FROM documents_' . $state . '
445
             WHERE path = ' . $db->quote($path) . '
446
        ');
447
            return $document;
448
        }
449
    }
450
451
452
}