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

Repository::publishOrUnpublishDocumentByPath()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 14
nc 4
nop 2
dl 0
loc 23
rs 9.0856
c 1
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
		foreach ($documents as $key => $document) {
269
			$documents = $this->setAssetsToDocumentFolders($document, $db, $documents, $key);
270
		}
271
		//dump($documents);
272
		return $documents;
273
	}
274
275
	/**
276
	 * Get all documents and folders in a certain path
277
	 *
278
	 * @param        $folderPath
279
	 * @param string $state
280
	 *
281
	 * @return array
282
	 * @throws \Exception
283
	 */
284
    public function getDocumentsByPath($folderPath, $state = 'published')
285
    {
286
    	if (!in_array($state, Document::$DOCUMENT_STATES)) {
287
    		throw new \Exception('Unsupported document state: ' . $state);
288
		}
289
        $db = $this->getContentDbHandle();
290
        $folderPathWithWildcard = $folderPath . '%';
291
292
        $sql = 'SELECT *
293
              FROM documents_' . $state . '
294
             WHERE `path` LIKE ' . $db->quote($folderPathWithWildcard) . '
295
               AND substr(`path`, ' . (strlen($folderPath) + 1) . ') NOT LIKE "%/%"
296
               AND path != ' . $db->quote($folderPath) . '
297
          ORDER BY `type` DESC, `path` ASC';
298
        $stmt = $this->getDbStatement($sql);
299
300
        $documents = $stmt->fetchAll(\PDO::FETCH_CLASS, '\library\storage\Document');
301
        foreach ($documents as $key => $document) {
302
			$documents = $this->setAssetsToDocumentFolders($document, $db, $documents, $key);
303
        }
304
        return $documents;
305
    }
306
307
308
    /**
309
     * @param $path
310
     * @return bool|Document
311
     */
312
    public function getDocumentContainerByPath($path)
313
    {
314
        $document = $this->getDocumentByPath($path, 'unpublished');
315
        if ($document === false) {
316
            return false;
317
        }
318
        $slugLength = strlen($document->slug);
319
        $containerPath = substr($path, 0, -$slugLength);
320
        if ($containerPath === '/') {
321
            return $this->getRootFolder();
322
        }
323
        if (substr($containerPath, -1) === '/'){
324
			$containerPath = substr($containerPath, 0, -1);
325
		}
326
        $containerFolder = $this->getDocumentByPath($containerPath, 'unpublished');
327
        return $containerFolder;
328
    }
329
330
	/**
331
	 * @param        $path
332
	 * @param string $state
333
	 *
334
	 * @return bool|\library\storage\Document
335
	 * @throws \Exception
336
	 */
337
    public function getDocumentByPath($path, $state = 'published')
338
    {
339
		if (!in_array($state, Document::$DOCUMENT_STATES)) {
340
			throw new \Exception('Unsupported document state: ' . $state);
341
		}
342
        $db = $this->getContentDbHandle();
343
        $document = $this->fetchDocument('
344
            SELECT *
345
              FROM documents_' .  $state . '
346
             WHERE path = ' . $db->quote($path) . '
347
        ');
348
        if ($document instanceof Document && $document->type === 'folder') {
349
            $document->dbHandle = $db;
350
            $document->documentStorage = new DocumentStorage($this);
351
        }
352
        return $document;
353
    }
354
355
	/**
356
	 * Returns the count of all documents stored in the db
357
	 *
358
	 * @param string $state
359
	 *
360
	 * @return int
361
	 * @throws \Exception
362
	 */
363
	public function getTotalDocumentCount($state = 'published')
364
	{
365
		if (!in_array($state, Document::$DOCUMENT_STATES)) {
366
			throw new \Exception('Unsupported document state: ' . $state);
367
		}
368
		$db = $this->getContentDbHandle();
369
		$stmt = $db->query('
370
			SELECT count(*)
371
			  FROM documents_' . $state . '
372
			 WHERE `type` != "folder"
373
		');
374
		$result = $stmt->fetch(\PDO::FETCH_ASSOC);
375
		if (!is_array($result )) {
376
			return 0;
377
		}
378
		return intval(current($result));
379
	}
380
381
	public function getPublishedDocumentsNoFolders()
382
	{
383
		$db = $this->getContentDbHandle();
384
		$sql = '
385
			SELECT *
386
			  FROM documents_published
387
			 WHERE `type` != "folder"
388
		';
389
		$stmt = $db->query($sql);
390
		$result = $stmt->fetchAll(\PDO::FETCH_CLASS, '\library\storage\Document');
391
		if ($stmt === false || !$stmt->execute()) {
392
			$errorInfo = $db->errorInfo();
393
			$errorMsg = $errorInfo[2];
394
			throw new \Exception('SQLite Exception: ' . $errorMsg . ' in SQL: <br /><pre>' . $sql . '</pre>');
395
		}
396
		return $result;
397
	}
398
399
	private function publishOrUnpublishDocumentByPath($path, $publish = true) {
400
		if ($publish) {
401
			$sql = '
402
				INSERT OR REPLACE INTO documents_published 
403
					  (`id`,`path`,`title`,`slug`,`type`,`documentType`,`documentTypeSlug`,`state`,`lastModificationDate`,`creationDate`,`publicationDate`,`lastModifiedBy`,`fields`,`bricks`,`dynamicBricks`)
404
				SELECT `id`,`path`,`title`,`slug`,`type`,`documentType`,`documentTypeSlug`,"published" as state,`lastModificationDate`,`creationDate`,' . time() . ' as publicationDate, `lastModifiedBy`,`fields`,`bricks`,`dynamicBricks`
405
				  FROM documents_unpublished
406
				 WHERE `path` = :path
407
			';
408
		} else {
409
			$sql = 'DELETE FROM documents_published
410
					  WHERE `path` = :path';
411
		}
412
		$db = $this->getContentDbHandle();
413
		$stmt = $db->prepare($sql);
414
		if ($stmt === false) {
415
			$errorInfo = $db->errorInfo();
416
			$errorMsg = $errorInfo[2];
417
			throw new \Exception('SQLite Exception: ' . $errorMsg . ' in SQL: <br /><pre>' . $sql . '</pre>');
418
		}
419
		$stmt->bindValue(':path', $path);
420
		$stmt->execute();
421
	}
422
423
	public function publishDocumentByPath($path)
424
	{
425
		$this->publishOrUnpublishDocumentByPath($path);
426
	}
427
428
	public function unpublishDocumentByPath($path)
429
	{
430
		$this->publishOrUnpublishDocumentByPath($path, false);
431
	}
432
433 View Code Duplication
	public function cleanPublishedDeletedDocuments()
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...
434
	{
435
		$db = $this->getContentDbHandle();
436
		$sql = '   DELETE FROM documents_published
437
						 WHERE documents_published.path IN (
438
						SELECT documents_published.path
439
						  FROM documents_published
440
					 LEFT JOIN documents_unpublished
441
							ON documents_unpublished.path = documents_published.path
442
						 WHERE documents_unpublished.path IS NULL
443
		)';
444
		$stmt = $db->query($sql);
445
		if ($stmt === false) {
446
			$errorInfo = $db->errorInfo();
447
			$errorMsg = $errorInfo[2];
448
			throw new \Exception('SQLite Exception: ' . $errorMsg . ' in SQL: <br /><pre>' . $sql . '</pre>');
449
		}
450
		$stmt->execute();
451
	}
452
453
	/**
454
     * Return the results of the query as array of Documents
455
     * @param $sql
456
     * @return array
457
     * @throws \Exception
458
     */
459
    protected function fetchAllDocuments($sql)
460
    {
461
        $stmt = $this->getDbStatement($sql);
462
        return $stmt->fetchAll(\PDO::FETCH_CLASS, '\library\storage\Document');
463
    }
464
465
    /**
466
     * Return the result of the query as Document
467
     * @param $sql
468
     * @return mixed
469
     * @throws \Exception
470
     */
471
    protected function fetchDocument($sql)
472
    {
473
        $stmt = $this->getDbStatement($sql);
474
        return $stmt->fetchObject('\library\storage\Document');
475
    }
476
477
    /**
478
     * Prepare the sql statement
479
     * @param $sql
480
     * @return \PDOStatement
481
     * @throws \Exception
482
     */
483 View Code Duplication
    protected function getDbStatement($sql)
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...
484
    {
485
        $db = $this->getContentDbHandle();
486
        $stmt = $db->query($sql);
487
        if ($stmt === false) {
488
            $errorInfo = $db->errorInfo();
489
            $errorMsg = $errorInfo[2];
490
            throw new \Exception('SQLite Exception: ' . $errorMsg . ' in SQL: <br /><pre>' . $sql . '</pre>');
491
        }
492
        return $stmt;
493
    }
494
495
    /**
496
     * Create a (non-existent) root folder Document and return it
497
     * @return Document
498
     */
499
    protected function getRootFolder()
500
    {
501
        $rootFolder = new Document();
502
        $rootFolder->path = '/';
503
        $rootFolder->type = 'folder';
504
        return $rootFolder;
505
    }
506
507
	/**
508
	 * Save the document to the database
509
	 *
510
	 * @param Document $documentObject
511
	 * @param string   $state
512
	 *
513
	 * @return bool
514
	 * @throws \Exception
515
	 * @internal param $path
516
	 */
517
    public function saveDocument($documentObject, $state = 'published')
518
    {
519
		if (!in_array($state, Document::$DOCUMENT_STATES)) {
520
			throw new \Exception('Unsupported document state: ' . $state);
521
		}
522
        $db = $this->getContentDbHandle();
523
        $stmt = $this->getDbStatement('
524
            INSERT OR REPLACE INTO documents_' . $state . ' (`path`,`title`,`slug`,`type`,`documentType`,`documentTypeSlug`,`state`,`lastModificationDate`,`creationDate`,`lastModifiedBy`,`fields`,`bricks`,`dynamicBricks`)
525
            VALUES(
526
              ' . $db->quote($documentObject->path) . ',
527
              ' . $db->quote($documentObject->title) . ',
528
              ' . $db->quote($documentObject->slug) . ',
529
              ' . $db->quote($documentObject->type) . ',
530
              ' . $db->quote($documentObject->documentType) . ',
531
              ' . $db->quote($documentObject->documentTypeSlug) . ',
532
              ' . $db->quote($documentObject->state) . ',
533
              ' . $db->quote($documentObject->lastModificationDate) . ',
534
              ' . $db->quote($documentObject->creationDate) . ',
535
              ' . $db->quote($documentObject->lastModifiedBy) . ',
536
              ' . $db->quote(json_encode($documentObject->fields)) . ',
537
              ' . $db->quote(json_encode($documentObject->bricks)) . ',
538
              ' . $db->quote(json_encode($documentObject->dynamicBricks)) . '
539
            )
540
        ');
541
        $result = $stmt->execute();
542
        return $result;
543
    }
544
545
	/**
546
	 * Delete the document from the database
547
	 * If it's a folder, also delete it's contents
548
	 *
549
	 * @param        $path
550
	 *
551
	 * @internal param string $state
552
	 *
553
	 */
554
    public function deleteDocumentByPath($path)
555
    {
556
        $db = $this->getContentDbHandle();
557
        $documentToDelete = $this->getDocumentByPath($path, 'unpublished');
558
        if ($documentToDelete instanceof Document) {
559
            if ($documentToDelete->type == 'document') {
560
                $stmt = $this->getDbStatement('
561
                    DELETE FROM documents_unpublished
562
                          WHERE path = ' . $db->quote($path) . '
563
                ');
564
                $stmt->execute();
565
            } elseif ($documentToDelete->type == 'folder') {
566
                $folderPathWithWildcard = $path . '%';
567
                $stmt = $this->getDbStatement('
568
                    DELETE FROM documents_unpublished
569
                          WHERE (path LIKE ' . $db->quote($folderPathWithWildcard) . '
570
                            AND substr(`path`, ' . (strlen($path) + 1) . ', 1) = "/")
571
                            OR path = ' . $db->quote($path) . '
572
                ');
573
                $stmt->execute();
574
            }
575
        }
576
    }
577
578
	/**
579
	 * @param $document
580
	 * @param $db
581
	 * @param $documents
582
	 * @param $key
583
	 *
584
	 * @return mixed
585
	 */
586
	private function setAssetsToDocumentFolders($document, $db, $documents, $key)
587
	{
588
		if ($document->type === 'folder') {
589
			$document->dbHandle = $db;
590
			$document->documentStorage = new DocumentStorage($this);
591
			$documents[$key] = $document;
592
		}
593
594
		return $documents;
595
	}
596
}