Passed
Push — develop ( 111896...eb49e9 )
by Jens
02:45
created

Repository::save()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 6
rs 9.4285
1
<?php
2
/**
3
 * User: Jens
4
 * Date: 30-1-2017
5
 * Time: 20:15
6
 */
7
8
namespace library\storage;
9
10
/**
11
 * Class Repository
12
 * @package library\storage
13
 * @property array sitemap
14
 * @property array applicationComponents
15
 * @property array documentTypes
16
 * @property array bricks
17
 * @property array imageSet
18
 * @property array images
19
 * @property array files
20
 * @property array users
21
 */
22
class Repository
23
{
24
    protected $storagePath;
25
26
    protected $fileBasedSubsets = array('sitemap', 'applicationComponents', 'documentTypes', 'bricks', 'imageSet', 'images', 'files', 'users');
27
28
    protected $sitemap;
29
    protected $sitemapChanges = false;
30
31
    protected $applicationComponents;
32
    protected $applicationComponentsChanges = false;
33
34
    protected $documentTypes;
35
    protected $documentTypesChanges = false;
36
37
    protected $bricks;
38
    protected $bricksChanges = false;
39
40
    protected $imageSet;
41
    protected $imageSetChanges = false;
42
43
    protected $images;
44
    protected $imagesChanges = false;
45
46
    protected $files;
47
    protected $filesChanges = false;
48
49
    protected $users;
50
    protected $usersChanges = false;
51
52
    protected $contentDbHandle;
53
54
    /**
55
     * Repository constructor.
56
     * @param $storagePath
57
     * @throws \Exception
58
     */
59
    public function __construct($storagePath)
60
    {
61
        $storagePath = realpath($storagePath);
62
        if (is_dir($storagePath) && $storagePath !== false) {
63
            $this->storagePath = $storagePath;
64
        } else {
65
            throw new \Exception('Repository not yet initialized.');
66
        }
67
    }
68
69
    /**
70
     * Creates the folder in which to create all storage related files
71
     *
72
     * @param $storagePath
73
     * @return bool
74
     */
75
    public static function create($storagePath)
76
    {
77
        return mkdir($storagePath);
78
    }
79
80
    /**
81
     * Initiates default storage
82
     * @throws \Exception
83
     */
84
    public function init()
85
    {
86
        $storageDefaultPath = realpath('../library/cc/install/_storage.json');
87
        $contentSqlPath = realpath('../library/cc/install/content.sql');
88
89
        $this->initConfigStorage($storageDefaultPath);
90
        $this->initContentDb($contentSqlPath);
91
92
        $this->save();
93
    }
94
95
    /**
96
     * Load filebased subset and return it's contents
97
     *
98
     * @param $name
99
     * @return mixed|string
100
     * @throws \Exception
101
     */
102
    public function __get($name)
103
    {
104
        if (isset($this->$name)) {
105 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...
106
                return $this->$name;
107
            } else {
108
                throw new \Exception('Trying to get undefined property from Repository: ' . $name);
109
            }
110 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...
111
            if (in_array($name, $this->fileBasedSubsets)) {
112
                return $this->loadSubset($name);
113
            } else {
114
                throw new \Exception('Trying to get undefined property from Repository: ' . $name);
115
            }
116
        }
117
    }
118
119
    /**
120
     * Set filebased subset contents
121
     * @param $name
122
     * @param $value
123
     * @throws \Exception
124
     */
125
    public function __set($name, $value)
126
    {
127
        if (in_array($name, $this->fileBasedSubsets)) {
128
            $this->$name = $value;
129
            $changes = $name . 'Changes';
130
            $this->$changes = true;
131
        } else {
132
            throw new \Exception('Trying to persist unknown subset in repository: ' . $name . ' <br /><pre>' . print_r($value, true) . '</pre>');
133
        }
134
    }
135
136
    /**
137
     * Persist all subsets
138
     */
139
    public function save()
140
    {
141
        array_map(function ($value) {
142
        	$this->saveSubset($value);
143
		}, $this->fileBasedSubsets);
144
    }
145
146
    /**
147
     * Persist subset to disk
148
     * @param $subset
149
     */
150
    protected function saveSubset($subset)
151
    {
152
		$changes = $subset . 'Changes';
153
		if ($this->$changes === true) {
154
			$json = json_encode($this->$subset, JSON_PRETTY_PRINT);
155
			$subsetStoragePath = $this->storagePath . DIRECTORY_SEPARATOR . $subset . '.json';
156
			file_put_contents($subsetStoragePath, $json);
157
158
			$this->$changes = false;
159
		}
160
    }
161
162
    /**
163
     * Load subset from disk
164
     * @param $subset
165
     * @return mixed|string
166
     */
167
    protected function loadSubset($subset)
168
    {
169
        $subsetStoragePath = $this->storagePath . DIRECTORY_SEPARATOR . $subset . '.json';
170
        $json = file_get_contents($subsetStoragePath);
171
        $json = json_decode($json);
172
        $this->$subset = $json;
173
        return $json;
174
    }
175
176
    /**
177
     * @param $contentSqlPath
178
     */
179
    protected function initContentDb($contentSqlPath)
180
    {
181
        $db = $this->getContentDbHandle();
182
        $sql = file_get_contents($contentSqlPath);
183
        $db->exec($sql);
184
    }
185
186
    /**
187
     * @param $storageDefaultPath
188
     */
189
    protected function initConfigStorage($storageDefaultPath)
190
    {
191
        $json = file_get_contents($storageDefaultPath);
192
        $json = json_decode($json);
193
        $this->sitemap = $json->sitemap;
194
        $this->sitemapChanges = true;
195
        $this->applicationComponents = $json->applicationComponents;
196
        $this->applicationComponentsChanges = true;
197
        $this->documentTypes = $json->documentTypes;
198
        $this->documentTypesChanges = true;
199
        $this->bricks = $json->bricks;
200
        $this->bricksChanges = true;
201
        $this->imageSet = $json->imageSet;
202
        $this->imageSetChanges = true;
203
        $this->images = $json->images;
204
        $this->imagesChanges = true;
205
        $this->files = $json->files;
206
        $this->filesChanges = true;
207
        $this->users = $json->users;
208
        $this->usersChanges = true;
209
    }
210
211
    /**
212
     * @return \PDO
213
     */
214
    public function getContentDbHandle()
215
    {
216
        if ($this->contentDbHandle === null) {
217
            $this->contentDbHandle = new \PDO('sqlite:' . $this->storagePath . DIRECTORY_SEPARATOR . 'content.db');
218
        }
219
        return $this->contentDbHandle;
220
    }
221
222
    /**
223
     * Get all documents
224
     * @return array
225
     */
226
    public function getDocuments()
227
    {
228
        return $this->getDocumentsByPath('/');
229
    }
230
231
    /**
232
     * Get all documents and folders in a certain path
233
     * @param $folderPath
234
     * @return array
235
     * @throws \Exception
236
     */
237
    public function getDocumentsByPath($folderPath)
238
    {
239
        $db = $this->getContentDbHandle();
240
        $folderPathWithWildcard = $folderPath . '%';
241
242
        $stmt = $this->getDbStatement('
243
            SELECT *
244
              FROM documents
245
             WHERE `path` LIKE ' . $db->quote($folderPathWithWildcard) . '
246
               AND substr(`path`, ' . (strlen($folderPath) + 1) . ') NOT LIKE "%/%"
247
               AND path != ' . $db->quote($folderPath) . '
248
          ORDER BY `type` DESC, `path` ASC
249
        ');
250
251
        $documents = $stmt->fetchAll(\PDO::FETCH_CLASS, '\library\storage\Document');
252
        foreach ($documents as $key => $document) {
253
            if ($document->type === 'folder') {
254
                $document->dbHandle = $db;
255
                $documents[$key] = $document;
256
            }
257
        }
258
        return $documents;
259
    }
260
261
262
    /**
263
     * @param $path
264
     * @return bool|Document
265
     */
266
    public function getDocumentContainerByPath($path)
267
    {
268
        $document = $this->getDocumentByPath($path);
269
        if ($document === false) {
270
            return false;
271
        }
272
        $slugLength = strlen($document->slug);
273
        $containerPath = substr($path, 0, -$slugLength);
274
        if ($containerPath === '/') {
275
            return $this->getRootFolder();
276
        }
277
        $containerFolder = $this->getDocumentByPath($containerPath);
278
        return $containerFolder;
279
    }
280
281
    /**
282
     * @param $path
283
     * @return bool|Document
284
     */
285
    public function getDocumentByPath($path)
286
    {
287
        $db = $this->getContentDbHandle();
288
        $document = $this->fetchDocument('
289
            SELECT *
290
              FROM documents
291
             WHERE path = ' . $db->quote($path) . '
292
        ');
293
        if ($document instanceof Document && $document->type === 'folder') {
294
            $document->dbHandle = $db;
295
        }
296
        return $document;
297
    }
298
299
	/**
300
	 * Returns the count of all documents stored in the db
301
	 * @return int
302
	 */
303
	public function getTotalDocumentCount()
304
	{
305
		$db = $this->getContentDbHandle();
306
		$stmt = $db->query('
307
			SELECT count(*)
308
			  FROM documents
309
		');
310
		$result = $stmt->fetch(\PDO::FETCH_ASSOC);
311
		if (!is_array($result )) {
312
			return 0;
313
		}
314
		return intval(current($result));
315
	}
316
317
	/**
318
     * Return the results of the query as array of Documents
319
     * @param $sql
320
     * @return array
321
     * @throws \Exception
322
     */
323
    protected function fetchAllDocuments($sql)
324
    {
325
        $stmt = $this->getDbStatement($sql);
326
        return $stmt->fetchAll(\PDO::FETCH_CLASS, '\library\storage\Document');
327
    }
328
329
    /**
330
     * Return the result of the query as Document
331
     * @param $sql
332
     * @return mixed
333
     * @throws \Exception
334
     */
335
    protected function fetchDocument($sql)
336
    {
337
        $stmt = $this->getDbStatement($sql);
338
        return $stmt->fetchObject('\library\storage\Document');
339
    }
340
341
    /**
342
     * Prepare the sql statement
343
     * @param $sql
344
     * @return \PDOStatement
345
     * @throws \Exception
346
     */
347
    protected function getDbStatement($sql)
348
    {
349
        $db = $this->getContentDbHandle();
350
        $stmt = $db->query($sql);
351 View Code Duplication
        if ($stmt === false) {
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...
352
            $errorInfo = $db->errorInfo();
353
            $errorMsg = $errorInfo[2];
354
            throw new \Exception('SQLite Exception: ' . $errorMsg . ' in SQL: <br /><pre>' . $sql . '</pre>');
355
        }
356
        return $stmt;
357
    }
358
359
    /**
360
     * Create a (non-existent) root folder Document and return it
361
     * @return Document
362
     */
363
    protected function getRootFolder()
364
    {
365
        $rootFolder = new Document();
366
        $rootFolder->path = '/';
367
        $rootFolder->type = 'folder';
368
        return $rootFolder;
369
    }
370
371
    /**
372
     * Save the document to the database
373
     * @param Document $documentObject
374
     * @return bool
375
     * @throws \Exception
376
     * @internal param $path
377
     */
378
    public function saveDocument($documentObject)
379
    {
380
        $db = $this->getContentDbHandle();
381
        $stmt = $this->getDbStatement('
382
            INSERT OR REPLACE INTO documents (`path`,`title`,`slug`,`type`,`documentType`,`documentTypeSlug`,`state`,`lastModificationDate`,`creationDate`,`lastModifiedBy`,`fields`,`bricks`,`dynamicBricks`)
383
            VALUES(
384
              ' . $db->quote($documentObject->path) . ',
385
              ' . $db->quote($documentObject->title) . ',
386
              ' . $db->quote($documentObject->slug) . ',
387
              ' . $db->quote($documentObject->type) . ',
388
              ' . $db->quote($documentObject->documentType) . ',
389
              ' . $db->quote($documentObject->documentTypeSlug) . ',
390
              ' . $db->quote($documentObject->state) . ',
391
              ' . $db->quote($documentObject->lastModificationDate) . ',
392
              ' . $db->quote($documentObject->creationDate) . ',
393
              ' . $db->quote($documentObject->lastModifiedBy) . ',
394
              ' . $db->quote(json_encode($documentObject->fields)) . ',
395
              ' . $db->quote(json_encode($documentObject->bricks)) . ',
396
              ' . $db->quote(json_encode($documentObject->dynamicBricks)) . '
397
            )
398
        ');
399
        $result = $stmt->execute();
400
        return $result;
401
    }
402
403
    /**
404
     * Delete the document from the database
405
     * If it's a folder, also delete it's contents
406
     * @param $path
407
     * @throws \Exception
408
     */
409
    public function deleteDocumentByPath($path)
410
    {
411
        $db = $this->getContentDbHandle();
412
        $documentToDelete = $this->getDocumentByPath($path);
413
        if ($documentToDelete instanceof Document) {
414
            if ($documentToDelete->type == 'document') {
415
                $stmt = $this->getDbStatement('
416
                    DELETE FROM documents
417
                          WHERE path = ' . $db->quote($path) . '
418
                ');
419
                $stmt->execute();
420
            } elseif ($documentToDelete->type == 'folder') {
421
                $folderPathWithWildcard = $path . '%';
422
                $stmt = $this->getDbStatement('
423
                    DELETE FROM documents
424
                          WHERE (path LIKE ' . $db->quote($folderPathWithWildcard) . '
425
                            AND substr(`path`, ' . (strlen($path) + 1) . ', 1) = "/")
426
                            OR path = ' . $db->quote($path) . '
427
                ');
428
                $stmt->execute();
429
            }
430
        }
431
    }
432
}