Passed
Push — develop ( f5d3ab...1cfb3a )
by Jens
03:36
created

Repository::getDocumentByPath()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 4
eloc 11
nc 3
nop 2
dl 0
loc 17
rs 9.2
c 2
b 0
f 0
1
<?php
2
/**
3
 * User: Jens
4
 * Date: 30-1-2017
5
 * Time: 20:15
6
 */
7
8
namespace library\storage;
9
use library\storage\storage\DocumentStorage;
10
11
/**
12
 * Class Repository
13
 * @package library\storage
14
 * @property array sitemap
15
 * @property array applicationComponents
16
 * @property array documentTypes
17
 * @property array bricks
18
 * @property array imageSet
19
 * @property array images
20
 * @property array files
21
 * @property array users
22
 */
23
class Repository
24
{
25
    protected $storagePath;
26
27
    protected $fileBasedSubsets = array('sitemap', 'applicationComponents', 'documentTypes', 'bricks', 'imageSet', 'images', 'files', 'users');
28
29
    protected $sitemap;
30
    protected $sitemapChanges = false;
31
32
    protected $applicationComponents;
33
    protected $applicationComponentsChanges = false;
34
35
    protected $documentTypes;
36
    protected $documentTypesChanges = false;
37
38
    protected $bricks;
39
    protected $bricksChanges = false;
40
41
    protected $imageSet;
42
    protected $imageSetChanges = false;
43
44
    protected $images;
45
    protected $imagesChanges = false;
46
47
    protected $files;
48
    protected $filesChanges = false;
49
50
    protected $users;
51
    protected $usersChanges = false;
52
53
    protected $contentDbHandle;
54
55
    /**
56
     * Repository constructor.
57
     * @param $storagePath
58
     * @throws \Exception
59
     */
60
    public function __construct($storagePath)
61
    {
62
        $storagePath = realpath($storagePath);
63
        if (is_dir($storagePath) && $storagePath !== false) {
64
            $this->storagePath = $storagePath;
65
        } else {
66
            throw new \Exception('Repository not yet initialized.');
67
        }
68
    }
69
70
    /**
71
     * Creates the folder in which to create all storage related files
72
     *
73
     * @param $storagePath
74
     * @return bool
75
     */
76
    public static function create($storagePath)
77
    {
78
        return mkdir($storagePath);
79
    }
80
81
    /**
82
     * Initiates default storage
83
     * @throws \Exception
84
     */
85
    public function init()
86
    {
87
        $storageDefaultPath = realpath('../library/cc/install/_storage.json');
88
        $contentSqlPath = realpath('../library/cc/install/content.sql');
89
90
        $this->initConfigStorage($storageDefaultPath);
91
        $this->initContentDb($contentSqlPath);
92
93
        $this->save();
94
    }
95
96
    /**
97
     * Load filebased subset and return it's contents
98
     *
99
     * @param $name
100
     * @return mixed|string
101
     * @throws \Exception
102
     */
103
    public function __get($name)
104
    {
105
        if (isset($this->$name)) {
106 View Code Duplication
            if (in_array($name, $this->fileBasedSubsets)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
107
                return $this->$name;
108
            } else {
109
                throw new \Exception('Trying to get undefined property from Repository: ' . $name);
110
            }
111 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
112
            if (in_array($name, $this->fileBasedSubsets)) {
113
                return $this->loadSubset($name);
114
            } else {
115
                throw new \Exception('Trying to get undefined property from Repository: ' . $name);
116
            }
117
        }
118
    }
119
120
    /**
121
     * Set filebased subset contents
122
     * @param $name
123
     * @param $value
124
     * @throws \Exception
125
     */
126
    public function __set($name, $value)
127
    {
128
        if (in_array($name, $this->fileBasedSubsets)) {
129
            $this->$name = $value;
130
            $changes = $name . 'Changes';
131
            $this->$changes = true;
132
        } else {
133
            throw new \Exception('Trying to persist unknown subset in repository: ' . $name . ' <br /><pre>' . print_r($value, true) . '</pre>');
134
        }
135
    }
136
137
    /**
138
     * Persist all subsets
139
     */
140
    public function save()
141
    {
142
        array_map(function ($value) {
143
        	$this->saveSubset($value);
144
		}, $this->fileBasedSubsets);
145
    }
146
147
    /**
148
     * Persist subset to disk
149
     * @param $subset
150
     */
151
    protected function saveSubset($subset)
152
    {
153
		$changes = $subset . 'Changes';
154
		if ($this->$changes === true) {
155
			$json = json_encode($this->$subset, JSON_PRETTY_PRINT);
156
			$subsetStoragePath = $this->storagePath . DIRECTORY_SEPARATOR . $subset . '.json';
157
			file_put_contents($subsetStoragePath, $json);
158
159
			$this->$changes = false;
160
		}
161
    }
162
163
    /**
164
     * Load subset from disk
165
     * @param $subset
166
     * @return mixed|string
167
     */
168
    protected function loadSubset($subset)
169
    {
170
        $subsetStoragePath = $this->storagePath . DIRECTORY_SEPARATOR . $subset . '.json';
171
        $json = file_get_contents($subsetStoragePath);
172
        $json = json_decode($json);
173
        $this->$subset = $json;
174
        return $json;
175
    }
176
177
    /**
178
     * @param $contentSqlPath
179
     */
180
    protected function initContentDb($contentSqlPath)
181
    {
182
        $db = $this->getContentDbHandle();
183
        $sql = file_get_contents($contentSqlPath);
184
        $db->exec($sql);
185
    }
186
187
    /**
188
     * @param $storageDefaultPath
189
     */
190
    protected function initConfigStorage($storageDefaultPath)
191
    {
192
        $json = file_get_contents($storageDefaultPath);
193
        $json = json_decode($json);
194
        $this->sitemap = $json->sitemap;
195
        $this->sitemapChanges = true;
196
        $this->applicationComponents = $json->applicationComponents;
197
        $this->applicationComponentsChanges = true;
198
        $this->documentTypes = $json->documentTypes;
199
        $this->documentTypesChanges = true;
200
        $this->bricks = $json->bricks;
201
        $this->bricksChanges = true;
202
        $this->imageSet = $json->imageSet;
203
        $this->imageSetChanges = true;
204
        $this->images = $json->images;
205
        $this->imagesChanges = true;
206
        $this->files = $json->files;
207
        $this->filesChanges = true;
208
        $this->users = $json->users;
209
        $this->usersChanges = true;
210
    }
211
212
    /**
213
     * @return \PDO
214
     */
215
    public function getContentDbHandle()
216
    {
217
        if ($this->contentDbHandle === null) {
218
            $this->contentDbHandle = new \PDO('sqlite:' . $this->storagePath . DIRECTORY_SEPARATOR . 'content.db');
219
        }
220
        return $this->contentDbHandle;
221
    }
222
223
	/**
224
	 * Get all documents
225
	 *
226
	 * @param string $state
227
	 *
228
	 * @return array
229
	 * @throws \Exception
230
	 */
231
    public function getDocuments($state = 'published')
232
    {
233
		if (!in_array($state, Document::$DOCUMENT_STATES)) {
234
			throw new \Exception('Unsupported document state: ' . $state);
235
		}
236
        return $this->getDocumentsByPath('/', $state);
237
    }
238
239
	public function getDocumentsWithState($folderPath = '/')
240
	{
241
		$db = $this->getContentDbHandle();
242
		$folderPathWithWildcard = $folderPath . '%';
243
244
		$ifRootIndex = 1;
245
		if ($folderPath == '/') {
246
			$ifRootIndex = 0;
247
		}
248
249
		$sql = '
250
            SELECT documents_unpublished.*,
251
            	   IFNULL(documents_published.state,"unpublished") as state,
252
            	   IFNULL(documents_published.publicationDate,NULL) as publicationDate,
253
            	   (documents_published.lastModificationDate != documents_unpublished.lastModificationDate) as unpublishedChanges 
254
              FROM documents_unpublished
255
		 LEFT JOIN documents_published
256
         		ON documents_published.path = documents_unpublished.path
257
             WHERE documents_unpublished.`path` LIKE ' . $db->quote($folderPathWithWildcard) . '
258
               AND substr(documents_unpublished.`path`, ' . (strlen($folderPath) + $ifRootIndex + 1) . ') NOT LIKE "%/%"
259
               AND length(documents_unpublished.`path`) > ' . (strlen($folderPath) + $ifRootIndex) . '
260
               AND documents_unpublished.path != ' . $db->quote($folderPath) . '
261
          ORDER BY documents_unpublished.`type` DESC, documents_unpublished.`path` ASC
262
        ';
263
		$stmt = $this->getDbStatement($sql);
264
265
266
267
		$documents = $stmt->fetchAll(\PDO::FETCH_CLASS, '\library\storage\Document');
268 View Code Duplication
		foreach ($documents as $key => $document) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
269
			if ($document->type === 'folder') {
270
				$document->dbHandle = $db;
271
				$document->documentStorage = new DocumentStorage($this);
272
				$documents[$key] = $document;
273
			}
274
		}
275
		//dump($documents);
276
		return $documents;
277
	}
278
279
	/**
280
	 * Get all documents and folders in a certain path
281
	 *
282
	 * @param        $folderPath
283
	 * @param string $state
284
	 *
285
	 * @return array
286
	 * @throws \Exception
287
	 */
288
    public function getDocumentsByPath($folderPath, $state = 'published')
289
    {
290
    	if (!in_array($state, Document::$DOCUMENT_STATES)) {
291
    		throw new \Exception('Unsupported document state: ' . $state);
292
		}
293
        $db = $this->getContentDbHandle();
294
        $folderPathWithWildcard = $folderPath . '%';
295
296
        $sql = 'SELECT *
297
              FROM documents_' . $state . '
298
             WHERE `path` LIKE ' . $db->quote($folderPathWithWildcard) . '
299
               AND substr(`path`, ' . (strlen($folderPath) + 1) . ') NOT LIKE "%/%"
300
               AND path != ' . $db->quote($folderPath) . '
301
          ORDER BY `type` DESC, `path` ASC';
302
        $stmt = $this->getDbStatement($sql);
303
304
        $documents = $stmt->fetchAll(\PDO::FETCH_CLASS, '\library\storage\Document');
305 View Code Duplication
        foreach ($documents as $key => $document) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
306
            if ($document->type === 'folder') {
307
                $document->dbHandle = $db;
308
                $document->documentStorage = new DocumentStorage($this);
309
                $documents[$key] = $document;
310
            }
311
        }
312
        return $documents;
313
    }
314
315
316
    /**
317
     * @param $path
318
     * @return bool|Document
319
     */
320
    public function getDocumentContainerByPath($path)
321
    {
322
        $document = $this->getDocumentByPath($path, 'unpublished');
323
        if ($document === false) {
324
            return false;
325
        }
326
        $slugLength = strlen($document->slug);
327
        $containerPath = substr($path, 0, -$slugLength);
328
        if ($containerPath === '/') {
329
            return $this->getRootFolder();
330
        }
331
        if (substr($containerPath, -1) === '/'){
332
			$containerPath = substr($containerPath, 0, -1);
333
		}
334
        $containerFolder = $this->getDocumentByPath($containerPath, 'unpublished');
335
        return $containerFolder;
336
    }
337
338
	/**
339
	 * @param        $path
340
	 * @param string $state
341
	 *
342
	 * @return bool|\library\storage\Document
343
	 * @throws \Exception
344
	 */
345
    public function getDocumentByPath($path, $state = 'published')
346
    {
347
		if (!in_array($state, Document::$DOCUMENT_STATES)) {
348
			throw new \Exception('Unsupported document state: ' . $state);
349
		}
350
        $db = $this->getContentDbHandle();
351
        $document = $this->fetchDocument('
352
            SELECT *
353
              FROM documents_' .  $state . '
354
             WHERE path = ' . $db->quote($path) . '
355
        ');
356
        if ($document instanceof Document && $document->type === 'folder') {
357
            $document->dbHandle = $db;
358
            $document->documentStorage = new DocumentStorage($this);
359
        }
360
        return $document;
361
    }
362
363
	/**
364
	 * Returns the count of all documents stored in the db
365
	 *
366
	 * @param string $state
367
	 *
368
	 * @return int
369
	 * @throws \Exception
370
	 */
371
	public function getTotalDocumentCount($state = 'published')
372
	{
373
		if (!in_array($state, Document::$DOCUMENT_STATES)) {
374
			throw new \Exception('Unsupported document state: ' . $state);
375
		}
376
		$db = $this->getContentDbHandle();
377
		$stmt = $db->query('
378
			SELECT count(*)
379
			  FROM documents_' . $state . '
380
			 WHERE `type` != "folder"
381
		');
382
		$result = $stmt->fetch(\PDO::FETCH_ASSOC);
383
		if (!is_array($result )) {
384
			return 0;
385
		}
386
		return intval(current($result));
387
	}
388
389
	public function getPublishedDocumentsNoFolders()
390
	{
391
		$db = $this->getContentDbHandle();
392
		$sql = '
393
			SELECT *
394
			  FROM documents_published
395
			 WHERE `type` != "folder"
396
		';
397
		$stmt = $db->query($sql);
398
		$result = $stmt->fetchAll(\PDO::FETCH_CLASS, '\library\storage\Document');
399
		if ($stmt === false || !$stmt->execute()) {
400
			$errorInfo = $db->errorInfo();
401
			$errorMsg = $errorInfo[2];
402
			throw new \Exception('SQLite Exception: ' . $errorMsg . ' in SQL: <br /><pre>' . $sql . '</pre>');
403
		}
404
		return $result;
405
	}
406
407 View Code Duplication
	public function publishDocumentByPath($path)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
408
	{
409
		$db = $this->getContentDbHandle();
410
		$sql = '
411
			INSERT OR REPLACE INTO documents_published 
412
				  (`id`,`path`,`title`,`slug`,`type`,`documentType`,`documentTypeSlug`,`state`,`lastModificationDate`,`creationDate`,`publicationDate`,`lastModifiedBy`,`fields`,`bricks`,`dynamicBricks`)
413
		    SELECT `id`,`path`,`title`,`slug`,`type`,`documentType`,`documentTypeSlug`,"published" as state,`lastModificationDate`,`creationDate`,' . time() . ' as publicationDate, `lastModifiedBy`,`fields`,`bricks`,`dynamicBricks`
414
		      FROM documents_unpublished
415
		     WHERE `path` = :path
416
		';
417
		$stmt = $db->prepare($sql);
418
		if ($stmt === false) {
419
			$errorInfo = $db->errorInfo();
420
			$errorMsg = $errorInfo[2];
421
			throw new \Exception('SQLite Exception: ' . $errorMsg . ' in SQL: <br /><pre>' . $sql . '</pre>');
422
		}
423
		$stmt->bindValue(':path', $path);
424
		$stmt->execute();
425
	}
426
427 View Code Duplication
	public function unpublishDocumentByPath($path)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
428
	{
429
		$db = $this->getContentDbHandle();
430
		$sql = 'DELETE FROM documents_published
431
					  WHERE `path` = :path';
432
		$stmt = $db->prepare($sql);
433
		if ($stmt === false) {
434
			$errorInfo = $db->errorInfo();
435
			$errorMsg = $errorInfo[2];
436
			throw new \Exception('SQLite Exception: ' . $errorMsg . ' in SQL: <br /><pre>' . $sql . '</pre>');
437
		}
438
		$stmt->bindValue(':path', $path);
439
		$stmt->execute();
440
	}
441
442
	/**
443
     * Return the results of the query as array of Documents
444
     * @param $sql
445
     * @return array
446
     * @throws \Exception
447
     */
448
    protected function fetchAllDocuments($sql)
449
    {
450
        $stmt = $this->getDbStatement($sql);
451
        return $stmt->fetchAll(\PDO::FETCH_CLASS, '\library\storage\Document');
452
    }
453
454
    /**
455
     * Return the result of the query as Document
456
     * @param $sql
457
     * @return mixed
458
     * @throws \Exception
459
     */
460
    protected function fetchDocument($sql)
461
    {
462
        $stmt = $this->getDbStatement($sql);
463
        return $stmt->fetchObject('\library\storage\Document');
464
    }
465
466
    /**
467
     * Prepare the sql statement
468
     * @param $sql
469
     * @return \PDOStatement
470
     * @throws \Exception
471
     */
472
    protected function getDbStatement($sql)
473
    {
474
        $db = $this->getContentDbHandle();
475
        $stmt = $db->query($sql);
476
        if ($stmt === false) {
477
            $errorInfo = $db->errorInfo();
478
            $errorMsg = $errorInfo[2];
479
            throw new \Exception('SQLite Exception: ' . $errorMsg . ' in SQL: <br /><pre>' . $sql . '</pre>');
480
        }
481
        return $stmt;
482
    }
483
484
    /**
485
     * Create a (non-existent) root folder Document and return it
486
     * @return Document
487
     */
488
    protected function getRootFolder()
489
    {
490
        $rootFolder = new Document();
491
        $rootFolder->path = '/';
492
        $rootFolder->type = 'folder';
493
        return $rootFolder;
494
    }
495
496
	/**
497
	 * Save the document to the database
498
	 *
499
	 * @param Document $documentObject
500
	 * @param string   $state
501
	 *
502
	 * @return bool
503
	 * @throws \Exception
504
	 * @internal param $path
505
	 */
506
    public function saveDocument($documentObject, $state = 'published')
507
    {
508
		if (!in_array($state, Document::$DOCUMENT_STATES)) {
509
			throw new \Exception('Unsupported document state: ' . $state);
510
		}
511
        $db = $this->getContentDbHandle();
512
        $stmt = $this->getDbStatement('
513
            INSERT OR REPLACE INTO documents_' . $state . ' (`path`,`title`,`slug`,`type`,`documentType`,`documentTypeSlug`,`state`,`lastModificationDate`,`creationDate`,`lastModifiedBy`,`fields`,`bricks`,`dynamicBricks`)
514
            VALUES(
515
              ' . $db->quote($documentObject->path) . ',
516
              ' . $db->quote($documentObject->title) . ',
517
              ' . $db->quote($documentObject->slug) . ',
518
              ' . $db->quote($documentObject->type) . ',
519
              ' . $db->quote($documentObject->documentType) . ',
520
              ' . $db->quote($documentObject->documentTypeSlug) . ',
521
              ' . $db->quote($documentObject->state) . ',
522
              ' . $db->quote($documentObject->lastModificationDate) . ',
523
              ' . $db->quote($documentObject->creationDate) . ',
524
              ' . $db->quote($documentObject->lastModifiedBy) . ',
525
              ' . $db->quote(json_encode($documentObject->fields)) . ',
526
              ' . $db->quote(json_encode($documentObject->bricks)) . ',
527
              ' . $db->quote(json_encode($documentObject->dynamicBricks)) . '
528
            )
529
        ');
530
        $result = $stmt->execute();
531
        return $result;
532
    }
533
534
	/**
535
	 * Delete the document from the database
536
	 * If it's a folder, also delete it's contents
537
	 *
538
	 * @param        $path
539
	 *
540
	 * @internal param string $state
541
	 *
542
	 */
543
    public function deleteDocumentByPath($path)
544
    {
545
        $db = $this->getContentDbHandle();
546
        $documentToDelete = $this->getDocumentByPath($path, 'unpublished');
547
        if ($documentToDelete instanceof Document) {
548
            if ($documentToDelete->type == 'document') {
549
                $stmt = $this->getDbStatement('
550
                    DELETE FROM documents_unpublished
551
                          WHERE path = ' . $db->quote($path) . '
552
                ');
553
                $stmt->execute();
554
            } elseif ($documentToDelete->type == 'folder') {
555
                $folderPathWithWildcard = $path . '%';
556
                $stmt = $this->getDbStatement('
557
                    DELETE FROM documents_unpublished
558
                          WHERE (path LIKE ' . $db->quote($folderPathWithWildcard) . '
559
                            AND substr(`path`, ' . (strlen($path) + 1) . ', 1) = "/")
560
                            OR path = ' . $db->quote($path) . '
561
                ');
562
                $stmt->execute();
563
            }
564
        }
565
    }
566
}