1
|
|
|
<?php |
|
|
|
|
2
|
|
|
/** |
3
|
|
|
* Quick end dirty solution only for demonstration purpose. |
4
|
|
|
*/ |
5
|
|
|
declare(strict_types=1); |
6
|
|
|
|
7
|
|
|
use StefanoTree\Exception\ValidationException; |
8
|
|
|
use StefanoTree\TreeInterface; |
9
|
|
|
|
10
|
|
|
session_start(); |
11
|
|
|
|
12
|
|
|
include_once __DIR__.'/../vendor/autoload.php'; |
13
|
|
|
|
14
|
|
|
$config = include_once __DIR__.'/config.php'; |
15
|
|
|
|
16
|
|
|
$dbAdapter = \Doctrine\DBAL\DriverManager::getConnection( |
17
|
|
|
$config['dbConnection'], |
18
|
|
|
new \Doctrine\DBAL\Configuration() |
19
|
|
|
); |
20
|
|
|
|
21
|
|
|
/************************************** |
22
|
|
|
* Tree Adapter |
23
|
|
|
**************************************/ |
24
|
|
|
$treeAdapter = \StefanoTree\NestedSet::factory( |
25
|
|
|
array( |
26
|
|
|
'tableName' => 'categories', |
27
|
|
|
'idColumnName' => 'id', |
28
|
|
|
'sequenceName' => 'categories_id_seq', |
29
|
|
|
'scopeColumnName' => 'group_id', |
30
|
|
|
), |
31
|
|
|
$dbAdapter |
32
|
|
|
); |
33
|
|
|
|
34
|
|
|
/*************************************** |
35
|
|
|
* Join example |
36
|
|
|
***************************************/ |
37
|
|
|
/* |
|
|
|
|
38
|
|
|
$select = $dbAdapter->createQueryBuilder(); |
39
|
|
|
$select->from('categories' ,'c') |
40
|
|
|
->select('c.*', '...') |
41
|
|
|
->leftJoin('c', 'metadata', 'm', 'm.id = c.id'); |
42
|
|
|
|
43
|
|
|
$adapter = $treeAdapter |
44
|
|
|
->getAdapter() |
45
|
|
|
->setDefaultDbSelect($select); |
46
|
|
|
*/ |
47
|
|
|
|
48
|
|
|
class Service |
|
|
|
|
49
|
|
|
{ |
50
|
|
|
private $treeAdapter; |
51
|
|
|
|
52
|
|
|
public function __construct(TreeInterface $treeAdapter) |
53
|
|
|
{ |
54
|
|
|
$this->treeAdapter = $treeAdapter; |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
private function getTreeAdapter(): TreeInterface |
58
|
|
|
{ |
59
|
|
|
return $this->treeAdapter; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
public function createRoot(array $data): void |
63
|
|
|
{ |
64
|
|
|
$errors = array(); |
65
|
|
|
|
66
|
|
|
$label = $data['label'] ?? ''; |
67
|
|
|
if (0 == strlen($label)) { |
68
|
|
|
$errors[] = 'Root Name cannot be empty.'; |
69
|
|
|
} elseif (15 < strlen($label)) { |
70
|
|
|
$errors[] = 'Root Name is too long. Max length is 15 characters.'; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
$scope = $data['scope'] ?? ''; |
74
|
|
|
if (0 === strlen($scope)) { |
75
|
|
|
$errors[] = 'Scope Name cannot be empty.'; |
76
|
|
|
} elseif (!preg_match('|^[1-9][0-9]*$|', $scope)) { |
77
|
|
|
$errors[] = 'Scope Name must be integer.'; |
78
|
|
|
} elseif (15 < strlen($scope)) { |
79
|
|
|
$errors[] = 'Scope Name is too long. Max length is 15 characters.'; |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
if (count($errors)) { |
83
|
|
|
throw new ValidationError($errors); |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
$data = array( |
87
|
|
|
'name' => $label, |
88
|
|
|
); |
89
|
|
|
|
90
|
|
|
try { |
91
|
|
|
$this->getTreeAdapter() |
92
|
|
|
->createRootNode($data, $scope); |
93
|
|
|
} catch (ValidationException $e) { |
94
|
|
|
throw new ValidationError([$e->getMessage()]); |
95
|
|
|
} |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
public function createNode(array $data): void |
99
|
|
|
{ |
100
|
|
|
$errors = array(); |
101
|
|
|
|
102
|
|
|
$targetId = $_POST['target_node_id'] ?? ''; |
103
|
|
|
if (0 === strlen($targetId)) { |
104
|
|
|
$errors[] = 'Target Node cannot be empty.'; |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
$label = $data['label'] ?? ''; |
108
|
|
|
if (0 == strlen($label)) { |
109
|
|
|
$errors[] = 'Name cannot be empty.'; |
110
|
|
|
} elseif (15 < strlen($label)) { |
111
|
|
|
$errors[] = 'Name is too long. Max length is 15 characters.'; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
$placement = $data['placement'] ?? ''; |
115
|
|
|
if (0 == strlen($placement)) { |
116
|
|
|
$errors[] = 'Placement cannot be empty.'; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
$data = array( |
120
|
|
|
'name' => $label, |
121
|
|
|
); |
122
|
|
|
|
123
|
|
|
if (count($errors)) { |
124
|
|
|
throw new ValidationError($errors); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
try { |
128
|
|
|
$this->getTreeAdapter() |
129
|
|
|
->addNode($targetId, $data, $placement); |
130
|
|
|
} catch (ValidationException $e) { |
131
|
|
|
throw new ValidationError([$e->getMessage()]); |
132
|
|
|
} |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
public function deleteNode(array $data): void |
136
|
|
|
{ |
137
|
|
|
$errors = array(); |
138
|
|
|
|
139
|
|
|
$id = $data['id'] ?? ''; |
140
|
|
|
|
141
|
|
|
if (0 == strlen($id)) { |
142
|
|
|
$errors[] = 'Id is missing. Cannot perform delete operation.'; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
if (count($errors)) { |
146
|
|
|
throw new ValidationError($errors); |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
$this->getTreeAdapter() |
150
|
|
|
->deleteBranch($id); |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
public function updateNode(array $data): void |
154
|
|
|
{ |
155
|
|
|
$errors = array(); |
156
|
|
|
|
157
|
|
|
$nodeId = $_POST['node_id'] ?? ''; |
158
|
|
|
if (0 === strlen($nodeId)) { |
159
|
|
|
$errors[] = 'Node cannot be empty.'; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
$label = $data['label'] ?? ''; |
163
|
|
|
if (0 == strlen($label)) { |
164
|
|
|
$errors[] = 'Name cannot be empty.'; |
165
|
|
|
} elseif (15 < strlen($label)) { |
166
|
|
|
$errors[] = 'Name is too long. Max length is 15 characters.'; |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
$data = array( |
170
|
|
|
'name' => $label, |
171
|
|
|
); |
172
|
|
|
|
173
|
|
|
if (count($errors)) { |
174
|
|
|
throw new ValidationError($errors); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
try { |
178
|
|
|
$this->getTreeAdapter() |
179
|
|
|
->updateNode($nodeId, $data); |
180
|
|
|
} catch (ValidationException $e) { |
181
|
|
|
throw new ValidationError([$e->getMessage()]); |
182
|
|
|
} |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
public function moveNode(array $data): void |
186
|
|
|
{ |
187
|
|
|
$errors = array(); |
188
|
|
|
|
189
|
|
|
$sourceId = $data['source_node_id'] ?? ''; |
190
|
|
|
if (0 == strlen($sourceId)) { |
191
|
|
|
$errors[] = 'Source Node cannot be empty.'; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
$targetId = $data['target_node_id'] ?? ''; |
195
|
|
|
if (0 == strlen($targetId)) { |
196
|
|
|
$errors[] = 'Target Node cannot be empty.'; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
$placement = $data['placement'] ?? ''; |
200
|
|
|
if (0 == strlen($placement)) { |
201
|
|
|
$errors[] = 'Placement cannot be empty.'; |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
if (count($errors)) { |
205
|
|
|
throw new ValidationError($errors); |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
try { |
209
|
|
|
$this->getTreeAdapter() |
210
|
|
|
->moveNode($sourceId, $targetId, $placement); |
211
|
|
|
} catch (ValidationException $e) { |
212
|
|
|
throw new ValidationError([$e->getMessage()]); |
213
|
|
|
} |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
public function getRoots(): array |
217
|
|
|
{ |
218
|
|
|
return $this->getTreeAdapter() |
219
|
|
|
->getRoots(); |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
public function getDescendants($nodeId): array |
223
|
|
|
{ |
224
|
|
|
return $this->getTreeAdapter() |
225
|
|
|
->getDescendantsQueryBuilder() |
226
|
|
|
->get($nodeId); |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
public function findDescendants(array $criteria): array |
230
|
|
|
{ |
231
|
|
|
$queryBuilder = $this->getTreeAdapter() |
232
|
|
|
->getDescendantsQueryBuilder(); |
233
|
|
|
|
234
|
|
|
$errors = array(); |
235
|
|
|
|
236
|
|
|
$nodeId = $criteria['node_id'] ?? ''; |
237
|
|
|
if (0 === strlen($nodeId)) { |
238
|
|
|
$errors[] = 'Node cannot be empty.'; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
$excludeFirstNLevel = $criteria['exclude_first_n_level'] ?? null; |
242
|
|
|
if (null !== $excludeFirstNLevel) { |
243
|
|
|
if (!preg_match('|^[0-9]*$|', $excludeFirstNLevel)) { |
244
|
|
|
$errors[] = 'Exclude First N Level must be positive integer,'; |
245
|
|
|
} else { |
246
|
|
|
$queryBuilder->excludeFirstNLevel((int) $excludeFirstNLevel); |
247
|
|
|
} |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
$levelLimit = $criteria['level_limit'] ?? null; |
251
|
|
|
if (null !== $levelLimit) { |
252
|
|
|
if (!preg_match('|^[0-9]*$|', $levelLimit)) { |
253
|
|
|
$errors[] = 'Level limit must be positive integer,'; |
254
|
|
|
} else { |
255
|
|
|
$queryBuilder->levelLimit((int) $levelLimit); |
256
|
|
|
} |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
$excludeBranch = $criteria['exclude_node_id'] ?? null; |
260
|
|
|
if (null !== $excludeBranch) { |
261
|
|
|
$queryBuilder->excludeBranch($excludeBranch); |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
if (count($errors)) { |
265
|
|
|
throw new ValidationError($errors); |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
return $queryBuilder->get($nodeId); |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
public function findAncestors(array $criteria): array |
272
|
|
|
{ |
273
|
|
|
$queryBuilder = $this->getTreeAdapter() |
274
|
|
|
->getAncestorsQueryBuilder(); |
275
|
|
|
|
276
|
|
|
$errors = array(); |
277
|
|
|
|
278
|
|
|
$nodeId = $criteria['node_id'] ?? ''; |
279
|
|
|
if (0 === strlen($nodeId)) { |
280
|
|
|
$errors[] = 'Node cannot be empty.'; |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
$excludeFirstNLevel = $criteria['exclude_first_n_level'] ?? null; |
284
|
|
|
if (null !== $excludeFirstNLevel) { |
285
|
|
|
if (!preg_match('|^[0-9]*$|', $excludeFirstNLevel)) { |
286
|
|
|
$errors[] = 'Exclude First N Level must be positive integer,'; |
287
|
|
|
} else { |
288
|
|
|
$queryBuilder->excludeFirstNLevel((int) $excludeFirstNLevel); |
289
|
|
|
} |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
$excludeLastNLevel = $criteria['exclude_last_n_level'] ?? null; |
293
|
|
|
if (null !== $excludeLastNLevel) { |
294
|
|
|
if (!preg_match('|^[0-9]*$|', $excludeLastNLevel)) { |
295
|
|
|
$errors[] = 'Exclude Last N Level must be positive integer,'; |
296
|
|
|
} else { |
297
|
|
|
$queryBuilder->excludeLastNLevel((int) $excludeLastNLevel); |
298
|
|
|
} |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
if (count($errors)) { |
302
|
|
|
throw new ValidationError($errors); |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
return $queryBuilder->get($nodeId); |
306
|
|
|
} |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
class ViewHelper |
|
|
|
|
310
|
|
|
{ |
311
|
|
|
public function escape($string): string |
312
|
|
|
{ |
313
|
|
|
return htmlspecialchars((string) $string); |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
public function renderTree(array $nodes): string |
317
|
|
|
{ |
318
|
|
|
$html = ''; |
319
|
|
|
|
320
|
|
|
$previousLevel = -1; |
321
|
|
|
foreach ($nodes as $node) { |
322
|
|
|
if ($previousLevel > $node['level']) { |
323
|
|
|
for ($i = $node['level']; $previousLevel > $i; ++$i) { |
324
|
|
|
$html = $html.'</li></ul>'; |
325
|
|
|
} |
326
|
|
|
$html = $html.'</li>'; |
327
|
|
|
} elseif ($previousLevel < $node['level']) { |
328
|
|
|
$html = $html.'<ul>'; |
329
|
|
|
} else { |
330
|
|
|
$html = $html.'</li>'; |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
$html = $html.'<li>'; |
334
|
|
|
$html = $html.'<span>'.$this->escape($node['name']).'</span>' |
335
|
|
|
.' <a href="/?action=delete&id='.$this->escape($node['id']).'" class="badge badge-danger">Delete</a>'; |
336
|
|
|
|
337
|
|
|
$previousLevel = $node['level']; |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
for ($i = -1; $previousLevel > $i; ++$i) { |
341
|
|
|
$html = $html.'</li></ul>'; |
342
|
|
|
} |
343
|
|
|
|
344
|
|
|
return $html; |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
public function renderBreadcrumbs(array $nodes): string |
348
|
|
|
{ |
349
|
|
|
$html = ''; |
350
|
|
|
|
351
|
|
|
foreach ($nodes as $node) { |
352
|
|
|
$html .= '<a class="breadcrumb-item" href="#">'.$this->escape($node['name']).'</a>'; |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
return '<nav class="breadcrumb">'.$html.'</nav>'; |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
public function renderSelectOptions(array $nodes): string |
359
|
|
|
{ |
360
|
|
|
$pathCache = array(); |
361
|
|
|
|
362
|
|
|
$html = ''; |
363
|
|
|
|
364
|
|
|
foreach ($nodes as $node) { |
365
|
|
|
if (!$node['parent_id']) { |
366
|
|
|
$pathCache[$node['id']] = '/'.$node['name']; |
367
|
|
|
} else { |
368
|
|
|
$pathCache[$node['id']] = $pathCache[$node['parent_id']].'/'.$node['name']; |
369
|
|
|
} |
370
|
|
|
$html .= '<option value="'.$this->escape($node['id']).'">'.$this->escape($pathCache[$node['id']]).'</option>'; |
371
|
|
|
} |
372
|
|
|
|
373
|
|
|
return '<option value="">---</option>'.$html; |
374
|
|
|
} |
375
|
|
|
|
376
|
|
|
public function renderPlacementOptions(): string |
377
|
|
|
{ |
378
|
|
|
$placements = array( |
379
|
|
|
TreeInterface::PLACEMENT_CHILD_TOP => 'Child Top', |
380
|
|
|
TreeInterface::PLACEMENT_CHILD_BOTTOM => 'Child Bottom', |
381
|
|
|
TreeInterface::PLACEMENT_TOP => 'Top', |
382
|
|
|
TreeInterface::PLACEMENT_BOTTOM => 'Bottom', |
383
|
|
|
); |
384
|
|
|
|
385
|
|
|
$html = ''; |
386
|
|
|
|
387
|
|
|
foreach ($placements as $id => $placement) { |
388
|
|
|
$html .= '<option value="'.$this->escape($id).'">'.$this->escape($placement).'</option>'; |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
return '<option value="">---</option>'.$html; |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
public function renderErrorMessages(array $errors): string |
395
|
|
|
{ |
396
|
|
|
$html = ''; |
397
|
|
|
|
398
|
|
|
foreach ($errors as $error) { |
399
|
|
|
$html .= '<li>'.$this->escape($error).'</li>'; |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
return '<div class="alert alert-danger"><ul class="error-container">'.$html.'</ul></div>'; |
403
|
|
|
} |
404
|
|
|
|
405
|
|
|
public function renderSuccessMessage(string $message): string |
406
|
|
|
{ |
407
|
|
|
return '<div class="alert alert-success">'.$this->escape($message).'</div>'; |
408
|
|
|
} |
409
|
|
|
|
410
|
|
|
public function renderFlashMessage(): string |
411
|
|
|
{ |
412
|
|
|
$message = $_SESSION['flashMessage'] ?? ''; |
413
|
|
|
|
414
|
|
|
if ($message) { |
415
|
|
|
unset($_SESSION['flashMessage']); |
416
|
|
|
|
417
|
|
|
return $this->renderSuccessMessage($message); |
418
|
|
|
} else { |
419
|
|
|
return ''; |
420
|
|
|
} |
421
|
|
|
} |
422
|
|
|
} |
423
|
|
|
|
424
|
|
|
class ValidationError extends \Exception |
|
|
|
|
425
|
|
|
{ |
426
|
|
|
private $errorMessages = array(); |
427
|
|
|
|
428
|
|
|
public function __construct(array $errorMessages = array()) |
429
|
|
|
{ |
430
|
|
|
$this->errorMessages = $errorMessages; |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
public function addError(string $error): void |
434
|
|
|
{ |
435
|
|
|
$this->errorMessages[] = $error; |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
public function addErrors(array $errors): void |
439
|
|
|
{ |
440
|
|
|
$this->errorMessages = array_merge(array_values($errors), $this->errorMessages); |
441
|
|
|
} |
442
|
|
|
|
443
|
|
|
public function getErrors(): array |
444
|
|
|
{ |
445
|
|
|
return $this->errorMessages; |
446
|
|
|
} |
447
|
|
|
} |
448
|
|
|
|
449
|
|
|
function setFlashMessageAndRedirect(string $message, string $url) |
450
|
|
|
{ |
451
|
|
|
$_SESSION['flashMessage'] = $message; |
452
|
|
|
$redirectUrl = (isset($_SERVER['HTTPS']) ? 'https' : 'http')."://$_SERVER[HTTP_HOST]$url"; |
453
|
|
|
header(sprintf('Location: %s', $redirectUrl)); |
454
|
|
|
die(); |
455
|
|
|
} |
456
|
|
|
|
457
|
|
|
/************************************ |
458
|
|
|
* Router |
459
|
|
|
***********************************/ |
460
|
|
|
$service = new Service($treeAdapter); |
461
|
|
|
|
462
|
|
|
try { |
463
|
|
|
switch ($_GET['action'] ?? '') { |
464
|
|
|
case 'create-scope': |
|
|
|
|
465
|
|
|
$service->createRoot($_POST); |
466
|
|
|
setFlashMessageAndRedirect('New root node and scope was successfully created.', '/'); |
467
|
|
|
break; |
468
|
|
|
case 'create-node': |
|
|
|
|
469
|
|
|
$service->createNode($_POST); |
470
|
|
|
setFlashMessageAndRedirect('New node was successfully created.', '/'); |
471
|
|
|
break; |
472
|
|
|
case 'move-node': |
|
|
|
|
473
|
|
|
$service->moveNode($_POST); |
474
|
|
|
setFlashMessageAndRedirect('Branch/Node was successfully moved.', '/'); |
475
|
|
|
break; |
476
|
|
|
case 'update-node': |
|
|
|
|
477
|
|
|
$service->updateNode($_POST); |
478
|
|
|
setFlashMessageAndRedirect('Node was successfully updated.', '/'); |
479
|
|
|
break; |
480
|
|
|
case 'delete': |
|
|
|
|
481
|
|
|
$service->deleteNode($_GET); |
482
|
|
|
setFlashMessageAndRedirect('Branch/Node was successfully deleted.', '/'); |
483
|
|
|
break; |
484
|
|
|
case 'descendant-test': |
|
|
|
|
485
|
|
|
$descendants = $service->findDescendants($_GET); |
486
|
|
|
$showDescendantTestBlock = true; |
487
|
|
|
break; |
488
|
|
|
case 'ancestor-test': |
|
|
|
|
489
|
|
|
$ancestors = $service->findAncestors($_GET); |
490
|
|
|
$showAncestorTestBlock = true; |
491
|
|
|
break; |
492
|
|
|
} |
493
|
|
|
} catch (ValidationError $e) { |
494
|
|
|
$errorMessage = $e->getErrors(); |
495
|
|
|
} |
496
|
|
|
|
497
|
|
|
/************************************ |
498
|
|
|
* View |
499
|
|
|
***********************************/ |
500
|
|
|
$wh = new ViewHelper(); |
501
|
|
|
?> |
502
|
|
|
|
503
|
|
|
<html> |
504
|
|
|
<head> |
505
|
|
|
<title>Demo</title> |
506
|
|
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous"> |
507
|
|
|
<style> |
508
|
|
|
.error-container { |
509
|
|
|
margin-bottom: 0; |
510
|
|
|
padding-left: 1rem; |
511
|
|
|
} |
512
|
|
|
</style> |
513
|
|
|
</head> |
514
|
|
|
<body> |
515
|
|
|
<div class="container-fluid"> |
516
|
|
|
<h1><a href="/">Demo</a></h1> |
517
|
|
|
|
518
|
|
|
<?php |
519
|
|
|
if ($errorMessage ?? false) { |
520
|
|
|
echo $wh->renderErrorMessages($errorMessage); |
521
|
|
|
} |
522
|
|
|
|
523
|
|
|
echo $wh->renderFlashMessage(); |
524
|
|
|
?> |
525
|
|
|
|
526
|
|
|
<div class="row"> |
527
|
|
|
<div class="col-sm-4"> |
528
|
|
|
<form action="/?action=create-scope" method="post"> |
529
|
|
|
<div class="form-group"> |
530
|
|
|
<label>Root name</label> |
531
|
|
|
<input type="text" name="label" class="form-control" /> |
532
|
|
|
</div> |
533
|
|
|
<div class="form-group"> |
534
|
|
|
<label>Scope name</label> |
535
|
|
|
<input type="text" name="scope" class="form-control" /> |
536
|
|
|
</div> |
537
|
|
|
<input type="submit" value="Create" class="btn btn-primary" /> |
538
|
|
|
</form> |
539
|
|
|
</div> |
540
|
|
|
</div> |
541
|
|
|
<?php |
542
|
|
|
foreach ($service->getRoots() as $root) { |
543
|
|
|
$nodes = $service->getDescendants($root['id']); ?> |
544
|
|
|
<hr /> |
545
|
|
|
<h2>Scope - <?php echo $wh->escape($root['group_id']); ?></h2> |
546
|
|
|
|
547
|
|
|
<div class="row"> |
548
|
|
|
<div class="col-sm-2"> |
549
|
|
|
<h3>Create</h3> |
550
|
|
|
<form action="/?action=create-node" method="post"> |
551
|
|
|
<div class="form-group"> |
552
|
|
|
<label>Name</label> |
553
|
|
|
<input type="text" name="label" class="form-control" /> |
554
|
|
|
</div> |
555
|
|
|
<div class="form-group"> |
556
|
|
|
<label>Target Node</label> |
557
|
|
|
<select name="target_node_id" class="form-control"><?php echo $wh->renderSelectOptions($nodes); ?></select> |
558
|
|
|
</div> |
559
|
|
|
<div class="form-group"> |
560
|
|
|
<label>Placement</label> |
561
|
|
|
<select name="placement" class="form-control"><?php echo $wh->renderPlacementOptions(); ?></select> |
562
|
|
|
</div> |
563
|
|
|
<div class="form-group"> |
564
|
|
|
<input type="submit" value="Create" class="btn btn-primary" /> |
565
|
|
|
</div> |
566
|
|
|
</form> |
567
|
|
|
</div> |
568
|
|
|
|
569
|
|
|
<div class="col-sm-2"> |
570
|
|
|
<h3>Move</h3> |
571
|
|
|
<form action="/?action=move-node" method="post"> |
572
|
|
|
<div class="form-group"> |
573
|
|
|
<label>Source Node</label> |
574
|
|
|
<select name="source_node_id" class="form-control"><?php echo $wh->renderSelectOptions($nodes); ?></select> |
575
|
|
|
</div> |
576
|
|
|
<div class="form-group"> |
577
|
|
|
<label>Target Node</label> |
578
|
|
|
<select name="target_node_id" class="form-control"><?php echo $wh->renderSelectOptions($nodes); ?></select> |
579
|
|
|
</div> |
580
|
|
|
<div class="form-group"> |
581
|
|
|
<label>Placement</label> |
582
|
|
|
<select name="placement" class="form-control"><?php echo $wh->renderPlacementOptions(); ?></select> |
583
|
|
|
</div> |
584
|
|
|
<input type="submit" value="Move" class="btn btn-primary" /> |
585
|
|
|
</form> |
586
|
|
|
</div> |
587
|
|
|
|
588
|
|
|
<div class="col-sm-2"> |
589
|
|
|
<h3>Update</h3> |
590
|
|
|
<form action="/?action=update-node" method="post"> |
591
|
|
|
<div class="form-group"> |
592
|
|
|
<label>Name</label> |
593
|
|
|
<input type="text" name="label" class="form-control" /> |
594
|
|
|
</div> |
595
|
|
|
<div class="form-group"> |
596
|
|
|
<label>Node</label> |
597
|
|
|
<select name="node_id" class="form-control"><?php echo $wh->renderSelectOptions($nodes); ?></select> |
598
|
|
|
</div> |
599
|
|
|
<input type="submit" value="Update" class="btn btn-primary" /> |
600
|
|
|
</form> |
601
|
|
|
</div> |
602
|
|
|
|
603
|
|
|
<div class="col-sm-3"> |
604
|
|
|
<h3>Descendant Test</h3> |
605
|
|
|
<form action="/" method="get"> |
606
|
|
|
<div class="form-group"> |
607
|
|
|
<label>Node</label> |
608
|
|
|
<select name="node_id" class="form-control"><?php echo $wh->renderSelectOptions($nodes); ?></select> |
609
|
|
|
</div> |
610
|
|
|
<div class="form-group"> |
611
|
|
|
<label>Exclude First N Level</label> |
612
|
|
|
<input type="number" min="0" step="1" name="exclude_first_n_level" class="form-control" /> |
613
|
|
|
</div> |
614
|
|
|
<div class="form-group"> |
615
|
|
|
<label>Level Limit</label> |
616
|
|
|
<input type="number" min="0" step="1" name="level_limit" class="form-control" /> |
617
|
|
|
</div> |
618
|
|
|
<div class="form-group"> |
619
|
|
|
<label>Exclude Branch</label> |
620
|
|
|
<select name="exclude_node_id" class="form-control"><?php echo $wh->renderSelectOptions($nodes); ?></select> |
621
|
|
|
</div> |
622
|
|
|
<input type="hidden" name="action" value="descendant-test" /> |
623
|
|
|
<input type="hidden" name="scope" value="<?php echo $wh->escape($root['group_id']); ?>" /> |
624
|
|
|
<input type="submit" value="Show" class="btn btn-primary" /> |
625
|
|
|
</form> |
626
|
|
|
</div> |
627
|
|
|
|
628
|
|
|
<div class="col-sm-3"> |
629
|
|
|
<h3>Ancestor Test</h3> |
630
|
|
|
<form action="/" method="get"> |
631
|
|
|
<div class="form-group"> |
632
|
|
|
<label>Node</label> |
633
|
|
|
<select name="node_id" class="form-control"><?php echo $wh->renderSelectOptions($nodes); ?></select> |
634
|
|
|
</div> |
635
|
|
|
<div class="form-group"> |
636
|
|
|
<label>Exclude First N Level</label> |
637
|
|
|
<input type="number" min="0" step="1" name="exclude_first_n_level" class="form-control" /> |
638
|
|
|
</div> |
639
|
|
|
<div class="form-group"> |
640
|
|
|
<label>Exclude Last N Level</label> |
641
|
|
|
<input type="number" min="0" step="1" name="exclude_last_n_level" class="form-control" /> |
642
|
|
|
</div> |
643
|
|
|
<input type="hidden" name="action" value="ancestor-test" /> |
644
|
|
|
<input type="hidden" name="scope" value="<?php echo $wh->escape($root['group_id']); ?>" /> |
645
|
|
|
<input type="submit" value="Show" class="btn btn-primary" /> |
646
|
|
|
</form> |
647
|
|
|
</div> |
648
|
|
|
</div> |
649
|
|
|
|
650
|
|
|
<hr /> |
651
|
|
|
|
652
|
|
|
<div class="row"> |
653
|
|
|
<div class="col-sm-6"> |
654
|
|
|
<h3>Whole Tree</h3> |
655
|
|
|
<?php echo $wh->renderTree($nodes); ?> |
656
|
|
|
</div> |
657
|
|
|
<div class="col-sm-6"> |
658
|
|
|
<?php |
659
|
|
|
if (($showDescendantTestBlock ?? false) && $root['group_id'] == $_GET['scope']) { |
660
|
|
|
?> |
661
|
|
|
<h3>Descendants Test Result</h3> |
662
|
|
|
<?php |
663
|
|
|
if (0 == count($descendants)) { |
664
|
|
|
echo $wh->renderErrorMessages(['No descendants was found']); |
665
|
|
|
} else { |
666
|
|
|
echo $wh->renderTree($descendants); |
667
|
|
|
} ?> |
668
|
|
|
<?php |
669
|
|
|
} ?> |
670
|
|
|
|
671
|
|
|
<?php |
672
|
|
|
if (($showAncestorTestBlock ?? false) && $root['group_id'] == $_GET['scope']) { |
673
|
|
|
?> |
674
|
|
|
<h3>Ancestors Test Result</h3> |
675
|
|
|
<?php |
676
|
|
|
if (0 == count($ancestors)) { |
677
|
|
|
echo $wh->renderErrorMessages(['No ancestors was found']); |
678
|
|
|
} else { |
679
|
|
|
echo $wh->renderBreadcrumbs($ancestors); |
680
|
|
|
echo $wh->renderTree($ancestors); |
681
|
|
|
} ?> |
682
|
|
|
<?php |
683
|
|
|
} ?> |
684
|
|
|
</div> |
685
|
|
|
</div> |
686
|
|
|
<?php |
687
|
|
|
}?> |
688
|
|
|
</div> |
689
|
|
|
</body> |
690
|
|
|
</html> |
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.