Completed
Push — openstreetmap ( c02ae5...190f01 )
by Greg
17:40 queued 08:16
created

HomePageController::userPage()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 16
nc 1
nop 1
dl 0
loc 24
rs 8.9713
c 0
b 0
f 0
1
<?php
2
/**
3
 * webtrees: online genealogy
4
 * Copyright (C) 2018 webtrees development team
5
 * This program is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
 * GNU General Public License for more details.
13
 * You should have received a copy of the GNU General Public License
14
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15
 */
16
declare(strict_types=1);
17
18
namespace Fisharebest\Webtrees\Http\Controllers;
19
20
use Fisharebest\Webtrees\Auth;
21
use Fisharebest\Webtrees\Database;
22
use Fisharebest\Webtrees\DebugBar;
23
use Fisharebest\Webtrees\Html;
24
use Fisharebest\Webtrees\I18N;
25
use Fisharebest\Webtrees\Module;
26
use Fisharebest\Webtrees\Module\ModuleBlockInterface;
27
use Fisharebest\Webtrees\Site;
28
use Fisharebest\Webtrees\Tree;
29
use Fisharebest\Webtrees\User;
30
use Symfony\Component\HttpFoundation\RedirectResponse;
31
use Symfony\Component\HttpFoundation\Request;
32
use Symfony\Component\HttpFoundation\Response;
33
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
34
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
35
36
/**
37
 * Controller for the user/tree's home page.
38
 */
39
class HomePageController extends AbstractBaseController {
40
	/**
41
	 * Show a form to edit block config options.
42
	 *
43
	 * @param Request $request
44
	 *
45
	 * @return Response
46
	 * @throws NotFoundHttpException
47
	 * @throws AccessDeniedHttpException
48
	 */
49
	public function treePageBlockEdit(Request $request): Response {
50
		/** @var Tree $tree */
51
		$tree = $request->attributes->get('tree');
52
53
		$block_id = (int) $request->get('block_id');
54
		$block    = $this->treeBlock($request);
55
		$title    = $block->getTitle() . ' — ' . I18N::translate('Preferences');
0 ignored issues
show
Bug introduced by
The method getTitle() does not exist on Fisharebest\Webtrees\Module\ModuleBlockInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Fisharebest\Webtrees\Module\ModuleBlockInterface. ( Ignorable by Annotation )

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

55
		$title    = $block->/** @scrutinizer ignore-call */ getTitle() . ' — ' . I18N::translate('Preferences');
Loading history...
56
57
		return $this->viewResponse('blocks/edit-config', [
58
			'block'      => $block,
59
			'block_id'   => $block_id,
60
			'cancel_url' => route('tree-page', ['ged' => $tree->getName()]),
61
			'title'      => $title,
62
		]);
63
	}
64
65
	/**
66
	 * Update block config options.
67
	 *
68
	 * @param Request $request
69
	 *
70
	 * @return RedirectResponse
71
	 */
72
	public function treePageBlockUpdate(Request $request): RedirectResponse {
73
		/** @var Tree $tree */
74
		$tree     = $request->attributes->get('tree');
75
		$block_id = (int) $request->get('block_id');
76
		$block    = $this->treeBlock($request);
77
78
		$block->configureBlock($block_id);
79
80
		return new RedirectResponse(route('tree-page', ['ged' => $tree->getName()]));
81
	}
82
83
	/**
84
	 * Load a block and check we have permission to edit it.
85
	 *
86
	 * @param Request $request
87
	 *
88
	 * @return ModuleBlockInterface
89
	 * @throws NotFoundHttpException
90
	 * @throws AccessDeniedHttpException
91
	 */
92
	private function treeBlock(Request $request): ModuleBlockInterface {
93
		/** @var User $user */
94
		$user     = $request->attributes->get('user');
95
		$block_id = (int) $request->get('block_id');
96
97
		$block_info = Database::prepare(
98
			"SELECT module_Name, user_id FROM `##block` WHERE block_id = :block_id"
99
		)->execute([
100
			'block_id' => $block_id,
101
		])->fetchOneRow();
102
103
		if ($block_info === null) {
104
			throw new NotFoundHttpException;
105
		}
106
107
		$block = Module::getModuleByName($block_info->module_name);
108
109
		if (!$block instanceof ModuleBlockInterface) {
110
			throw new NotFoundHttpException;
111
		}
112
113
		if ($block_info->user_id !== $user->getUserId() && !Auth::isAdmin()) {
114
			throw new AccessDeniedHttpException;
115
		}
116
117
		return $block;
118
	}
119
120
	/**
121
	 * Show a form to edit block config options.
122
	 *
123
	 * @param Request $request
124
	 *
125
	 * @return Response
126
	 * @throws NotFoundHttpException
127
	 * @throws AccessDeniedHttpException
128
	 */
129
	public function userPageBlockEdit(Request $request): Response {
130
		/** @var Tree $tree */
131
		$tree = $request->attributes->get('tree');
132
133
		$block_id = (int) $request->get('block_id');
134
		$block    = $this->userBlock($request);
135
		$title    = $block->getTitle() . ' — ' . I18N::translate('Preferences');
136
137
		return $this->viewResponse('blocks/edit-config', [
138
			'block'      => $block,
139
			'block_id'   => $block_id,
140
			'cancel_url' => route('user-page', ['ged' => $tree->getName()]),
141
			'title'      => $title,
142
		]);
143
	}
144
145
	/**
146
	 * Update block config options.
147
	 *
148
	 * @param Request $request
149
	 *
150
	 * @return RedirectResponse
151
	 */
152
	public function userPageBlockUpdate(Request $request): RedirectResponse {
153
		/** @var Tree $tree */
154
		$tree     = $request->attributes->get('tree');
155
		$block_id = (int) $request->get('block_id');
156
		$block    = $this->userBlock($request);
157
158
		$block->configureBlock($block_id);
159
160
		return new RedirectResponse(route('user-page', ['ged' => $tree->getName()]));
161
	}
162
163
	/**
164
	 * Load a block and check we have permission to edit it.
165
	 *
166
	 * @param Request $request
167
	 *
168
	 * @return ModuleBlockInterface
169
	 * @throws NotFoundHttpException
170
	 * @throws AccessDeniedHttpException
171
	 */
172
	private function userBlock(Request $request): ModuleBlockInterface {
173
		/** @var User $user */
174
		$user = $request->attributes->get('user');
175
176
		$block_id = (int) $request->get('block_id');
177
178
		$block_info = Database::prepare(
179
			"SELECT module_Name, user_id FROM `##block` WHERE block_id = :block_id"
180
		)->execute([
181
			'block_id' => $block_id,
182
		])->fetchOneRow();
183
184
		if ($block_info === null) {
185
			throw new NotFoundHttpException('This block does not exist');
186
		}
187
188
		$block = Module::getModuleByName($block_info->module_name);
189
190
		if (!$block instanceof ModuleBlockInterface) {
191
			throw new NotFoundHttpException($block_info->module_name . ' is not a block');
192
		}
193
194
		$block_owner_id = (int) $block_info->user_id;
195
196
		if ($block_owner_id !== $user->getUserId() && !Auth::isAdmin()) {
197
			throw new AccessDeniedHttpException('You are not allowed to edit this block');
198
		}
199
200
		return $block;
201
	}
202
203
	/**
204
	 * Show a tree's page.
205
	 *
206
	 * @param Request $request
207
	 *
208
	 * @return Response
209
	 */
210
	public function treePage(Request $request): Response {
211
		/** @var Tree $tree */
212
		$tree = $request->attributes->get('tree');
213
214
		$tree_id      = $tree->getTreeId();
215
		$access_level = Auth::accessLevel($tree);
216
		$main_blocks  = $this->getBlocksForTreePage($tree_id, $access_level, 'main');
217
		$side_blocks  = $this->getBlocksForTreePage($tree_id, $access_level, 'side');
218
		$title        = e($tree->getTitle());
219
220
		// @TODO - ModuleBlockInterface::getBlock() currently relies on these globals
221
		global $WT_TREE, $ctype, $controller;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
222
		$WT_TREE    = $tree;
223
		$ctype      = 'gedcom';
224
		$controller = $this;
225
226
		return $this->viewResponse('tree-page', [
227
			'main_blocks' => $main_blocks,
228
			'side_blocks' => $side_blocks,
229
			'title'       => $title,
230
			'meta_robots' => 'index,follow',
231
		]);
232
	}
233
234
	/**
235
	 * Load block asynchronously.
236
	 *
237
	 * @param Request $request
238
	 *
239
	 * @return Response
240
	 */
241
	public function treePageBlock(Request $request): Response {
242
		/** @var Tree $tree */
243
		$tree     = $request->attributes->get('tree');
244
		$block_id = (int) $request->get('block_id');
245
246
		$block = Database::prepare(
247
			"SELECT * FROM `##block` WHERE block_id = :block_id AND gedcom_id = :tree_id AND user_id IS NULL"
248
		)->execute([
249
			'block_id' => $block_id,
250
			'tree_id'  => $tree->getTreeId(),
251
		])->fetchOneRow();
252
253
		$module = $this->getBlockModule($tree, $block_id);
254
255
		if ($block === null || $module === null) {
256
			return new Response('', 404);
257
		}
258
259
		// @TODO - ModuleBlockInterface::getBlock() currently relies on these globals
260
		global $WT_TREE, $ctype, $controller;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
261
		$WT_TREE    = $tree;
262
		$ctype      = 'gedcom';
263
		$controller = $this;
264
265
		$html = view('layouts/ajax', [
266
			'content' => $module->getBlock($block_id, true),
267
		]);
268
269
270
		// Use HTTP headers and some jQuery to add debug to the current page.
271
		DebugBar::sendDataInHeaders();
272
273
		return new Response($html);
274
	}
275
276
	/**
277
	 * Show a form to edit the default blocks for new trees.
278
	 *
279
	 * @param Request $request
280
	 *
281
	 * @return Response
282
	 */
283
	public function treePageDefaultEdit(Request $request): Response {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

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

283
	public function treePageDefaultEdit(/** @scrutinizer ignore-unused */ Request $request): Response {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
284
		$main_blocks = $this->getBlocksForTreePage(-1, Auth::PRIV_NONE, 'main');
285
		$side_blocks = $this->getBlocksForTreePage(-1, Auth::PRIV_NONE, 'side');
286
		$all_blocks  = $this->getAvailableTreeBlocks();
287
		$title       = I18N::translate('Set the default blocks for new family trees');
288
		$url_cancel  = route('admin-control-panel');
289
		$url_save    = route('tree-page-default-update');
290
291
		return $this->viewResponse('edit-blocks-page', [
292
			'all_blocks'  => $all_blocks,
293
			'can_reset'   => false,
294
			'main_blocks' => $main_blocks,
295
			'side_blocks' => $side_blocks,
296
			'title'       => $title,
297
			'url_cancel'  => $url_cancel,
298
			'url_save'    => $url_save,
299
		]);
300
	}
301
302
	/**
303
	 * Save updated default blocks for new trees.
304
	 *
305
	 * @param Request $request
306
	 *
307
	 * @return RedirectResponse
308
	 */
309
	public function treePageDefaultUpdate(Request $request): RedirectResponse {
310
		$main_blocks = (array) $request->get('main');
311
		$side_blocks = (array) $request->get('side');
312
313
		$this->updateTreeBlocks(-1, $main_blocks, $side_blocks);
314
315
		return new RedirectResponse(route('admin-control-panel'));
316
	}
317
318
	/**
319
	 * Show a form to edit the blocks on a tree's page.
320
	 *
321
	 * @param Request $request
322
	 *
323
	 * @return Response
324
	 */
325
	public function treePageEdit(Request $request): Response {
326
		/** @var Tree $tree */
327
		$tree = $request->attributes->get('tree');
328
329
		$main_blocks = $this->getBlocksForTreePage($tree->getTreeId(), Auth::accessLevel($tree), 'main');
330
		$side_blocks = $this->getBlocksForTreePage($tree->getTreeId(), Auth::accessLevel($tree), 'side');
331
		$all_blocks  = $this->getAvailableTreeBlocks();
332
		$title       = I18N::translate('Change the “Home page” blocks');
333
		$url_cancel  = route('tree-page', ['ged' => $tree->getName()]);
334
		$url_save    = route('tree-page-update', ['ged' => $tree->getName()]);
335
336
		return $this->viewResponse('edit-blocks-page', [
337
			'all_blocks'  => $all_blocks,
338
			'can_reset'   => true,
339
			'main_blocks' => $main_blocks,
340
			'side_blocks' => $side_blocks,
341
			'title'       => $title,
342
			'url_cancel'  => $url_cancel,
343
			'url_save'    => $url_save,
344
		]);
345
	}
346
347
	/**
348
	 * Save updated blocks on a tree's page.
349
	 *
350
	 * @param Request $request
351
	 *
352
	 * @return RedirectResponse
353
	 */
354
	public function treePageUpdate(Request $request): RedirectResponse {
355
		/** @var Tree $tree */
356
		$tree = $request->attributes->get('tree');
357
358
		$defaults = (bool) $request->get('defaults');
359
360
		if ($defaults) {
361
			$main_blocks = $this->getBlocksForTreePage(-1, AUth::PRIV_NONE, 'main');
362
			$side_blocks = $this->getBlocksForTreePage(-1, Auth::PRIV_NONE, 'side');
363
		} else {
364
			$main_blocks = (array) $request->get('main');
365
			$side_blocks = (array) $request->get('side');
366
		}
367
368
		$this->updateTreeBlocks($tree->getTreeId(), $main_blocks, $side_blocks);
369
370
		return new RedirectResponse(route('tree-page', ['ged' => $tree->getName()]));
371
	}
372
373
	/**
374
	 * Show a users's page.
375
	 *
376
	 * @param Request $request
377
	 *
378
	 * @return Response
379
	 */
380
	public function userPage(Request $request) {
381
		/** @var Tree $tree */
382
		$tree = $request->attributes->get('tree');
383
384
		/** @var User $user */
385
		$user = $request->attributes->get('user');
386
387
		$tree_id      = $tree->getTreeId();
388
		$user_id      = $user->getUserId();
389
		$access_level = Auth::accessLevel($tree, $user);
390
		$main_blocks  = $this->getBlocksForUserPage($tree_id, $user_id, $access_level, 'main');
391
		$side_blocks  = $this->getBlocksForUserPage($tree_id, $user_id, $access_level, 'side');
392
		$title        = I18N::translate('My page');
393
394
		// @TODO - ModuleBlockInterface::getBlock() currently relies on these globals
395
		global $WT_TREE, $ctype, $controller;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
396
		$WT_TREE    = $tree;
397
		$ctype      = 'user';
398
		$controller = $this;
399
400
		return $this->viewResponse('user-page', [
401
			'main_blocks' => $main_blocks,
402
			'side_blocks' => $side_blocks,
403
			'title'       => $title,
404
		]);
405
	}
406
407
	/**
408
	 * Load block asynchronously.
409
	 *
410
	 * @param Request $request
411
	 *
412
	 * @return Response
413
	 */
414
	public function userPageBlock(Request $request): Response {
415
		/** @var Tree $tree */
416
		$tree = $request->attributes->get('tree');
417
418
		/** @var User $user */
419
		$user = $request->attributes->get('user');
420
421
		$block_id = (int) $request->get('block_id');
422
423
		$block = Database::prepare(
424
			"SELECT * FROM `##block` WHERE block_id = :block_id AND gedcom_id IS NULL AND user_id = :user_id"
425
		)->execute([
426
			'block_id' => $block_id,
427
			'user_id'  => $user->getUserId(),
428
		])->fetchOneRow();
429
430
		$module = $this->getBlockModule($tree, $block_id);
431
432
		if ($block === null || $module === null) {
433
			return new Response('Block not found', 404);
434
		}
435
436
		// @TODO - ModuleBlockInterface::getBlock() relies on these globals :-(
437
		global $WT_TREE, $ctype, $controller;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
438
		$WT_TREE    = $tree;
439
		$ctype      = 'user';
440
		$controller = $this;
441
442
		$html = view('layouts/ajax', [
443
			'content' => $module->getBlock($block_id, true),
444
		]);
445
446
		// Use HTTP headers and some jQuery to add debug to the current page.
447
		DebugBar::sendDataInHeaders();
448
449
		return new Response($html);
450
	}
451
452
	/**
453
	 * Show a form to edit the default blocks for new uesrs.
454
	 *
455
	 * @param Request $request
456
	 *
457
	 * @return Response
458
	 */
459
	public function userPageDefaultEdit(Request $request): Response {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

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

459
	public function userPageDefaultEdit(/** @scrutinizer ignore-unused */ Request $request): Response {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
460
		$main_blocks = $this->getBlocksForUserPage(-1, -1, Auth::PRIV_NONE, 'main');
461
		$side_blocks = $this->getBlocksForUserPage(-1, -1, Auth::PRIV_NONE, 'side');
462
		$all_blocks  = $this->getAvailableUserBlocks();
463
		$title       = I18N::translate('Set the default blocks for new users');
464
		$url_cancel  = route('admin-users');
465
		$url_save    = route('user-page-default-update');
466
467
		return $this->viewResponse('edit-blocks-page', [
468
			'all_blocks'  => $all_blocks,
469
			'can_reset'   => false,
470
			'main_blocks' => $main_blocks,
471
			'side_blocks' => $side_blocks,
472
			'title'       => $title,
473
			'url_cancel'  => $url_cancel,
474
			'url_save'    => $url_save,
475
		]);
476
	}
477
478
	/**
479
	 * Save the updated default blocks for new users.
480
	 *
481
	 * @param Request $request
482
	 *
483
	 * @return RedirectResponse
484
	 */
485
	public function userPageDefaultUpdate(Request $request): RedirectResponse {
486
		$main_blocks = (array) $request->get('main');
487
		$side_blocks = (array) $request->get('side');
488
489
		$this->updateUserBlocks(-1, $main_blocks, $side_blocks);
490
491
		return new RedirectResponse(route('admin-control-panel'));
492
	}
493
494
	/**
495
	 * Show a form to edit the blocks on the user's page.
496
	 *
497
	 * @param Request $request
498
	 *
499
	 * @return Response
500
	 */
501
	public function userPageEdit(Request $request): Response {
502
		/** @var Tree $tree */
503
		$tree = $request->attributes->get('tree');
504
505
		/** @var User $user */
506
		$user = $request->attributes->get('user');
507
508
		$main_blocks = $this->getBlocksForUserPage($tree->getTreeId(), $user->getUserId(), Auth::accessLevel($tree, $user), 'main');
509
		$side_blocks = $this->getBlocksForUserPage($tree->getTreeId(), $user->getUserId(), Auth::accessLevel($tree, $user), 'side');
510
		$all_blocks  = $this->getAvailableUserBlocks();
511
		$title       = I18N::translate('Change the “My page” blocks');
512
		$url_cancel  = route('user-page', ['ged' => $tree->getName()]);
513
		$url_save    = route('user-page-update', ['ged' => $tree->getName()]);
514
515
		return $this->viewResponse('edit-blocks-page', [
516
			'all_blocks'  => $all_blocks,
517
			'can_reset'   => true,
518
			'main_blocks' => $main_blocks,
519
			'side_blocks' => $side_blocks,
520
			'title'       => $title,
521
			'url_cancel'  => $url_cancel,
522
			'url_save'    => $url_save,
523
		]);
524
	}
525
526
	/**
527
	 * Save the updted blocks on a user's page.
528
	 *
529
	 * @param Request $request
530
	 *
531
	 * @return RedirectResponse
532
	 */
533
	public function userPageUpdate(Request $request): RedirectResponse {
534
		/** @var Tree $tree */
535
		$tree = $request->attributes->get('tree');
536
537
		/** @var User $user */
538
		$user = $request->attributes->get('user');
539
540
		$defaults = (bool) $request->get('defaults');
541
542
		if ($defaults) {
543
			$main_blocks = $this->getBlocksForUserPage(-1, -1, AUth::PRIV_NONE, 'main');
544
			$side_blocks = $this->getBlocksForUserPage(-1, -1, Auth::PRIV_NONE, 'side');
545
		} else {
546
			$main_blocks = (array) $request->get('main');
547
			$side_blocks = (array) $request->get('side');
548
		}
549
550
		$this->updateUserBlocks($user->getUserId(), $main_blocks, $side_blocks);
551
552
		return new RedirectResponse(route('user-page', ['ged' => $tree->getName()]));
553
	}
554
555
	/**
556
	 * Show a form to edit the blocks for another user's page.
557
	 *
558
	 * @param Request $request
559
	 *
560
	 * @return Response
561
	 */
562
	public function userPageUserEdit(Request $request): Response {
563
		$user_id     = (int) $request->get('user_id');
564
		$user        = User::find($user_id);
565
		$main_blocks = $this->getBlocksForUserPage(-1, $user_id, Auth::PRIV_NONE, 'main');
566
		$side_blocks = $this->getBlocksForUserPage(-1, $user_id, Auth::PRIV_NONE, 'side');
567
		$all_blocks  = $this->getAvailableUserBlocks();
568
		$title       = I18N::translate('Change the blocks on this user’s “My page”') . ' - ' . e($user->getUserName());
569
		$url_cancel  = route('admin-users');
570
		$url_save    = route('user-page-user-update', ['user_id' => $user_id]);
571
572
		return $this->viewResponse('edit-blocks-page', [
573
			'all_blocks'  => $all_blocks,
574
			'can_reset'   => false,
575
			'main_blocks' => $main_blocks,
576
			'side_blocks' => $side_blocks,
577
			'title'       => $title,
578
			'url_cancel'  => $url_cancel,
579
			'url_save'    => $url_save,
580
		]);
581
	}
582
583
	/**
584
	 * Save the updated blocks for another user's page.
585
	 *
586
	 * @param Request $request
587
	 *
588
	 * @return RedirectResponse
589
	 */
590
	public function userPageUserUpdate(Request $request): RedirectResponse {
591
		$user_id     = (int) $request->get('user_id');
592
		$main_blocks = (array) $request->get('main');
593
		$side_blocks = (array) $request->get('side');
594
595
		$this->updateUserBlocks($user_id, $main_blocks, $side_blocks);
596
597
		return new RedirectResponse(route('admin-control-panel'));
598
	}
599
600
	/**
601
	 * Get a specific block.
602
	 *
603
	 * @param Tree $tree
604
	 * @param int  $block_id
605
	 *
606
	 * @return ModuleBlockInterface|null
607
	 */
608
	private function getBlockModule(Tree $tree, int $block_id) {
609
		$active_blocks = Module::getActiveBlocks($tree);
610
611
		$module_name = Database::prepare(
612
			"SELECT module_name FROM `##block`" .
613
			" JOIN  `##module` USING (module_name)" .
614
			" WHERE block_id = :block_id AND status = 'enabled'"
615
		)->execute([
616
			'block_id' => $block_id,
617
		])->fetchOne();
618
619
		return $active_blocks[$module_name] ?? null;
620
	}
621
622
	/**
623
	 * Get all the available blocks for a tree page.
624
	 *
625
	 * @return ModuleBlockInterface[]
626
	 */
627
	private function getAvailableTreeBlocks(): array {
628
		$blocks = Module::getAllModulesByComponent('block');
629
		$blocks = array_filter($blocks, function (ModuleBlockInterface $block) {
630
			return $block->isGedcomBlock();
631
		});
632
633
		return $blocks;
634
	}
635
636
	/**
637
	 * Get all the available blocks for a user page.
638
	 *
639
	 * @return ModuleBlockInterface[]
640
	 */
641
	private function getAvailableUserBlocks(): array {
642
		$blocks = Module::getAllModulesByComponent('block');
643
		$blocks = array_filter($blocks, function (ModuleBlockInterface $block) {
644
			return $block->isUserBlock();
645
		});
646
647
		return $blocks;
648
	}
649
650
	/**
651
	 * Get the blocks for a specified tree (or the default tree).
652
	 *
653
	 * @param int    $tree_id
654
	 * @param int    $access_level
655
	 * @param string $location "main" or "side"
656
	 *
657
	 * @return ModuleBlockInterface[]
658
	 */
659
	private function getBlocksForTreePage(int $tree_id, int $access_level, string $location): array {
660
		$rows = Database::prepare(
661
			"SELECT SQL_CACHE block_id, module_name" .
662
			" FROM  `##block`" .
663
			" JOIN  `##module` USING (module_name)" .
664
			" JOIN  `##module_privacy` USING (module_name, gedcom_id)" .
665
			" WHERE gedcom_id = :tree_id" .
666
			" AND   status = 'enabled'" .
667
			" AND   location = :location" .
668
			" AND   access_level >= :access_level" .
669
			" ORDER BY location, block_order"
670
		)->execute([
671
			'access_level' => $access_level,
672
			'location'     => $location,
673
			'tree_id'      => $tree_id,
674
		])->fetchAssoc();
675
676
		return $this->filterActiveBlocks($rows, $this->getAvailableTreeBlocks());
677
	}
678
679
	/**
680
	 * Get the blocks for a specified user (or the default user).
681
	 *
682
	 * @param int    $tree_id
683
	 * @param int    $user_id
684
	 * @param int    $access_level
685
	 * @param string $location "main" or "side"
686
	 *
687
	 * @return ModuleBlockInterface[]
688
	 */
689
	private function getBlocksForUserPage(int $tree_id, int $user_id, int $access_level, string $location): array {
690
		$rows = Database::prepare(
691
			"SELECT SQL_CACHE block_id, module_name" .
692
			" FROM  `##block`" .
693
			" JOIN  `##module` USING (module_name)" .
694
			" JOIN  `##module_privacy` USING (module_name)" .
695
			" WHERE user_id = :user_id" .
696
			" AND   status = 'enabled'" .
697
			" AND   location = :location" .
698
			" AND   `##module_privacy`.gedcom_id = :tree_id" .
699
			" AND   access_level >= :access_level" .
700
			" ORDER BY block_order"
701
		)->execute([
702
			'access_level' => $access_level,
703
			'location'     => $location,
704
			'user_id'      => $user_id,
705
			'tree_id'      => $tree_id,
706
		])->fetchAssoc();
707
708
		return $this->filterActiveBlocks($rows, $this->getAvailableUserBlocks());
709
	}
710
711
	/**
712
	 * Take a list of block names, and return block (module) objects.
713
	 *
714
	 * @param string[] $blocks
715
	 * @param array    $active_blocks
716
	 *
717
	 * @return ModuleBlockInterface[]
718
	 */
719
	private function filterActiveBlocks(array $blocks, array $active_blocks): array {
720
		return array_filter(array_map(function (string $module_name) use ($active_blocks) {
721
			return $active_blocks[$module_name] ?? false;
722
		}, $blocks));
723
	}
724
725
	/**
726
	 * Save the updated blocks for a tree.
727
	 *
728
	 * @param int   $tree_id
729
	 * @param array $main_blocks
730
	 * @param array $side_blocks
731
	 */
732
	private function updateTreeBlocks(int $tree_id, array $main_blocks, array $side_blocks) {
733
		$existing_block_ids = Database::prepare(
734
			"SELECT block_id FROM `##block` WHERE gedcom_id = :tree_id"
735
		)->execute([
736
			'tree_id' => $tree_id,
737
		])->fetchOneColumn();
738
739
		// Deleted blocks
740
		foreach ($existing_block_ids as $existing_block_id) {
741
			if (!in_array($existing_block_id, $main_blocks) && !in_array($existing_block_id, $side_blocks)) {
742
				Database::prepare(
743
					"DELETE FROM `##block_setting` WHERE block_id = :block_id"
744
				)->execute([
745
					'block_id' => $existing_block_id,
746
				]);
747
				Database::prepare(
748
					"DELETE FROM `##block` WHERE block_id = :block_id"
749
				)->execute([
750
					'block_id' => $existing_block_id,
751
				]);
752
			}
753
		}
754
755
		$updates = [
756
			'main' => $main_blocks,
757
			'side' => $side_blocks,
758
		];
759
760
		foreach ($updates as $location => $updated_blocks) {
761
			foreach ($updated_blocks as $block_order => $block_id) {
762
				if (is_numeric($block_id)) {
763
					// Updated block
764
					Database::prepare(
765
						"UPDATE `##block`" .
766
						" SET block_order = :block_order, location = :location" .
767
						" WHERE block_id = :block_id"
768
					)->execute([
769
						'block_order' => $block_order,
770
						'block_id'    => $block_id,
771
						'location'    => $location,
772
					]);
773
				} else {
774
					// New block
775
					Database::prepare(
776
						"INSERT INTO `##block` (gedcom_id, location, block_order, module_name)" .
777
						" VALUES (:tree_id, :location, :block_order, :module_name)"
778
					)->execute([
779
						'tree_id'     => $tree_id,
780
						'location'    => $location,
781
						'block_order' => $block_order,
782
						'module_name' => $block_id,
783
					]);
784
				}
785
			}
786
		}
787
	}
788
789
	/**
790
	 * Save the updated blocks for a user.
791
	 *
792
	 * @param int   $user_id
793
	 * @param array $main_blocks
794
	 * @param array $side_blocks
795
	 */
796
	private function updateUserBlocks(int $user_id, array $main_blocks, array $side_blocks) {
797
		$existing_block_ids = Database::prepare(
798
			"SELECT block_id FROM `##block` WHERE user_id = :user_id"
799
		)->execute([
800
			'user_id' => $user_id,
801
		])->fetchOneColumn();
802
803
		// Deleted blocks
804
		foreach ($existing_block_ids as $existing_block_id) {
805
			if (!in_array($existing_block_id, $main_blocks) && !in_array($existing_block_id, $side_blocks)) {
806
				Database::prepare(
807
					"DELETE FROM `##block_setting` WHERE block_id = :block_id"
808
				)->execute([
809
					'block_id' => $existing_block_id,
810
				]);
811
				Database::prepare(
812
					"DELETE FROM `##block` WHERE block_id = :block_id"
813
				)->execute([
814
					'block_id' => $existing_block_id,
815
				]);
816
			}
817
		}
818
819
		foreach (['main' => $main_blocks, 'side' => $side_blocks] as $location => $updated_blocks) {
820
			foreach ($updated_blocks as $block_order => $block_id) {
821
				if (is_numeric($block_id)) {
822
					// Updated block
823
					Database::prepare(
824
						"UPDATE `##block`" .
825
						" SET block_order = :block_order, location = :location" .
826
						" WHERE block_id = :block_id"
827
					)->execute([
828
						'block_order' => $block_order,
829
						'block_id'    => $block_id,
830
						'location'    => $location,
831
					]);
832
				} else {
833
					// New block
834
					Database::prepare(
835
						"INSERT INTO `##block` (user_id, location, block_order, module_name)" .
836
						" VALUES (:user_id, :location, :block_order, :module_name)"
837
					)->execute([
838
						'user_id'     => $user_id,
839
						'location'    => $location,
840
						'block_order' => $block_order,
841
						'module_name' => $block_id,
842
					]);
843
				}
844
			}
845
		}
846
	}
847
}
848