Passed
Push — develop ( 4e5c80...f2da7f )
by Jens
02:23
created

ContentRepository::getDocumentByPath()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 5
eloc 12
c 2
b 1
f 0
nc 4
nop 3
dl 0
loc 19
rs 8.8571
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');
0 ignored issues
show
Bug introduced by
The call to PDO::__construct() has too few arguments starting with username. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

32
                $this->contentDbHandle = /** @scrutinizer ignore-call */ new \PDO('sqlite:' . $this->storagePath . DIRECTORY_SEPARATOR . 'content.db');

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

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