Passed
Push — master ( a81c3e...8e0e1b )
by Greg
06:21
created

HomePageService::treeBlock()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 16
c 1
b 0
f 0
nc 4
nop 2
dl 0
loc 28
rs 9.4222
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2019 webtrees development team
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\Services;
21
22
use Fisharebest\Webtrees\Auth;
23
use Fisharebest\Webtrees\Contracts\UserInterface;
24
use Fisharebest\Webtrees\Exceptions\HttpAccessDeniedException;
25
use Fisharebest\Webtrees\Exceptions\HttpNotFoundException;
26
use Fisharebest\Webtrees\Module\ModuleBlockInterface;
27
use Fisharebest\Webtrees\Module\ModuleInterface;
28
use Fisharebest\Webtrees\Tree;
29
use Illuminate\Database\Capsule\Manager as DB;
30
use Illuminate\Support\Collection;
31
use Psr\Http\Message\ServerRequestInterface;
32
use stdClass;
33
34
use function assert;
35
use function is_numeric;
36
37
/**
38
 * Logic and content for the home-page blocks.
39
 */
40
class HomePageService
41
{
42
    /** @var ModuleService */
43
    private $module_service;
44
45
    /**
46
     * HomePageController constructor.
47
     *
48
     * @param ModuleService $module_service
49
     */
50
    public function __construct(ModuleService $module_service)
51
    {
52
        $this->module_service = $module_service;
53
    }
54
55
    /**
56
     * Load a block and check we have permission to edit it.
57
     *
58
     * @param ServerRequestInterface $request
59
     * @param UserInterface          $user
60
     *
61
     * @return ModuleBlockInterface
62
     */
63
    public function treeBlock(ServerRequestInterface $request, UserInterface $user): ModuleBlockInterface
64
    {
65
        $tree = $request->getAttribute('tree');
66
        assert($tree instanceof Tree);
67
68
        $block_id = (int) $request->getQueryParams()['block_id'];
69
70
        $block = DB::table('block')
71
            ->where('block_id', '=', $block_id)
72
            ->where('gedcom_id', '=', $tree->id())
73
            ->whereNull('user_id')
74
            ->first();
75
76
        if (!$block instanceof stdClass) {
77
            throw new HttpNotFoundException();
78
        }
79
80
        $module = $this->module_service->findByName($block->module_name);
81
82
        if (!$module instanceof ModuleBlockInterface) {
83
            throw new HttpNotFoundException();
84
        }
85
86
        if ($block->user_id !== $user->id() && !Auth::isAdmin()) {
87
            throw new HttpAccessDeniedException();
88
        }
89
90
        return $module;
91
    }
92
93
    /**
94
     * Load a block and check we have permission to edit it.
95
     *
96
     * @param ServerRequestInterface $request
97
     * @param UserInterface          $user
98
     *
99
     * @return ModuleBlockInterface
100
     */
101
    public function userBlock(ServerRequestInterface $request, UserInterface $user): ModuleBlockInterface
102
    {
103
        $block_id = (int) $request->getQueryParams()['block_id'];
104
105
        $block = DB::table('block')
106
            ->where('block_id', '=', $block_id)
107
            ->where('user_id', '=', $user->id())
108
            ->whereNull('gedcom_id')
109
            ->first();
110
111
        if (!$block instanceof stdClass) {
112
            throw new HttpNotFoundException('This block does not exist');
113
        }
114
115
        $module = $this->module_service->findByName($block->module_name);
116
117
        if (!$module instanceof ModuleBlockInterface) {
118
            throw new HttpNotFoundException($block->module_name . ' is not a block');
119
        }
120
121
        $block_owner_id = (int) $block->user_id;
122
123
        if ($block_owner_id !== $user->id() && !Auth::isAdmin()) {
124
            throw new HttpAccessDeniedException('You are not allowed to edit this block');
125
        }
126
127
        return $module;
128
    }
129
130
    /**
131
     * Get a specific block.
132
     *
133
     * @param Tree $tree
134
     * @param int  $block_id
135
     *
136
     * @return ModuleBlockInterface
137
     */
138
    public function getBlockModule(Tree $tree, int $block_id): ModuleBlockInterface
139
    {
140
        $active_blocks = $this->module_service->findByComponent(ModuleBlockInterface::class, $tree, Auth::user());
141
142
        $module_name = DB::table('block')
143
            ->where('block_id', '=', $block_id)
144
            ->value('module_name');
145
146
        $block = $active_blocks->first(static function (ModuleInterface $module) use ($module_name): bool {
147
            return $module->name() === $module_name;
148
        });
149
150
        if ($block instanceof ModuleBlockInterface) {
151
            return $block;
152
        }
153
154
        throw new HttpNotFoundException('Block not found');
155
    }
156
157
    /**
158
     * Get all the available blocks for a tree page.
159
     *
160
     * @return Collection<string,ModuleBlockInterface>
161
     */
162
    public function availableTreeBlocks(): Collection
163
    {
164
        return $this->module_service->findByInterface(ModuleBlockInterface::class, false, true)
165
            ->filter(static function (ModuleBlockInterface $block): bool {
166
                return $block->isTreeBlock();
167
            })
168
            ->mapWithKeys(static function (ModuleBlockInterface $block): array {
169
                return [$block->name() => $block];
170
            });
171
    }
172
173
    /**
174
     * Get all the available blocks for a user page.
175
     *
176
     * @return Collection<string,ModuleBlockInterface>
177
     */
178
    public function availableUserBlocks(): Collection
179
    {
180
        return $this->module_service->findByInterface(ModuleBlockInterface::class, false, true)
181
            ->filter(static function (ModuleBlockInterface $block): bool {
182
                return $block->isUserBlock();
183
            })
184
            ->mapWithKeys(static function (ModuleBlockInterface $block): array {
185
                return [$block->name() => $block];
186
            });
187
    }
188
189
    /**
190
     * Get the blocks for a specified tree.
191
     *
192
     * @param int    $tree_id
193
     * @param string $location "main" or "side"
194
     *
195
     * @return Collection<string,ModuleBlockInterface>
196
     */
197
    public function treeBlocks(int $tree_id, string $location): Collection
198
    {
199
        $rows = DB::table('block')
200
            ->where('gedcom_id', '=', $tree_id)
201
            ->where('location', '=', $location)
202
            ->orderBy('block_order')
203
            ->pluck('module_name', 'block_id');
204
205
        return $this->filterActiveBlocks($rows, $this->availableTreeBlocks());
206
    }
207
208
    /**
209
     * Make sure that default blocks exist for a tree.
210
     *
211
     * @return void
212
     */
213
    public function checkDefaultTreeBlocksExist(): void
214
    {
215
        $has_blocks = DB::table('block')
216
            ->where('gedcom_id', '=', -1)
217
            ->exists();
218
219
        // No default settings?  Create them.
220
        if (!$has_blocks) {
221
            foreach ([ModuleBlockInterface::MAIN_BLOCKS, ModuleBlockInterface::SIDE_BLOCKS] as $location) {
222
                foreach (ModuleBlockInterface::DEFAULT_TREE_PAGE_BLOCKS[$location] as $block_order => $class) {
223
                    $module_name = $this->module_service->findByInterface($class)->first()->name();
224
225
                    DB::table('block')->insert([
226
                        'gedcom_id'   => -1,
227
                        'location'    => $location,
228
                        'block_order' => $block_order,
229
                        'module_name' => $module_name,
230
                    ]);
231
                }
232
            }
233
        }
234
    }
235
236
    /**
237
     * Get the blocks for a specified user.
238
     *
239
     * @param int    $user_id
240
     * @param string $location "main" or "side"
241
     *
242
     * @return Collection<string,ModuleBlockInterface>
243
     */
244
    public function userBlocks(int $user_id, string $location): Collection
245
    {
246
        $rows = DB::table('block')
247
            ->where('user_id', '=', $user_id)
248
            ->where('location', '=', $location)
249
            ->orderBy('block_order')
250
            ->pluck('module_name', 'block_id');
251
252
        return $this->filterActiveBlocks($rows, $this->availableUserBlocks());
253
    }
254
255
    /**
256
     * Make sure that default blocks exist for a user.
257
     *
258
     * @return void
259
     */
260
    public function checkDefaultUserBlocksExist(): void
261
    {
262
        $has_blocks = DB::table('block')
263
            ->where('user_id', '=', -1)
264
            ->exists();
265
266
        // No default settings?  Create them.
267
        if (!$has_blocks) {
268
            foreach ([ModuleBlockInterface::MAIN_BLOCKS, ModuleBlockInterface::SIDE_BLOCKS] as $location) {
269
                foreach (ModuleBlockInterface::DEFAULT_USER_PAGE_BLOCKS[$location] as $block_order => $class) {
270
                    $module_name = $this->module_service->findByInterface($class)->first()->name();
271
272
                    DB::table('block')->insert([
273
                        'user_id'     => -1,
274
                        'location'    => $location,
275
                        'block_order' => $block_order,
276
                        'module_name' => $module_name,
277
                    ]);
278
                }
279
            }
280
        }
281
    }
282
283
    /**
284
     * Save the updated blocks for a user.
285
     *
286
     * @param int                $user_id
287
     * @param Collection<string> $main_block_ids
288
     * @param Collection<string> $side_block_ids
289
     *
290
     * @return void
291
     */
292
    public function updateUserBlocks(int $user_id, Collection $main_block_ids, Collection $side_block_ids): void
293
    {
294
        $existing_block_ids = DB::table('block')
295
            ->where('user_id', '=', $user_id)
296
            ->pluck('block_id');
297
298
        // Deleted blocks
299
        foreach ($existing_block_ids as $existing_block_id) {
300
            if (!$main_block_ids->contains($existing_block_id) && !$side_block_ids->contains($existing_block_id)) {
301
                DB::table('block_setting')
302
                    ->where('block_id', '=', $existing_block_id)
303
                    ->delete();
304
305
                DB::table('block')
306
                    ->where('block_id', '=', $existing_block_id)
307
                    ->delete();
308
            }
309
        }
310
311
        $updates = [
312
            ModuleBlockInterface::MAIN_BLOCKS => $main_block_ids,
313
            ModuleBlockInterface::SIDE_BLOCKS => $side_block_ids,
314
        ];
315
316
        foreach ($updates as $location => $updated_blocks) {
317
            foreach ($updated_blocks as $block_order => $block_id) {
318
                if (is_numeric($block_id)) {
319
                    // Updated block
320
                    DB::table('block')
321
                        ->where('block_id', '=', $block_id)
322
                        ->update([
323
                            'block_order' => $block_order,
324
                            'location'    => $location,
325
                        ]);
326
                } else {
327
                    // New block
328
                    DB::table('block')->insert([
329
                        'user_id'     => $user_id,
330
                        'location'    => $location,
331
                        'block_order' => $block_order,
332
                        'module_name' => $block_id,
333
                    ]);
334
                }
335
            }
336
        }
337
    }
338
339
    /**
340
     * Save the updated blocks for a tree.
341
     *
342
     * @param int                $tree_id
343
     * @param Collection<string> $main_block_ids
344
     * @param Collection<string> $side_block_ids
345
     *
346
     * @return void
347
     */
348
    public function updateTreeBlocks(int $tree_id, Collection $main_block_ids, Collection $side_block_ids): void
349
    {
350
        $existing_block_ids = DB::table('block')
351
            ->where('gedcom_id', '=', $tree_id)
352
            ->pluck('block_id');
353
354
        // Deleted blocks
355
        foreach ($existing_block_ids as $existing_block_id) {
356
            if (!$main_block_ids->contains($existing_block_id) && !$side_block_ids->contains($existing_block_id)) {
357
                DB::table('block_setting')
358
                    ->where('block_id', '=', $existing_block_id)
359
                    ->delete();
360
361
                DB::table('block')
362
                    ->where('block_id', '=', $existing_block_id)
363
                    ->delete();
364
            }
365
        }
366
367
        $updates = [
368
            ModuleBlockInterface::MAIN_BLOCKS => $main_block_ids,
369
            ModuleBlockInterface::SIDE_BLOCKS => $side_block_ids,
370
        ];
371
372
        foreach ($updates as $location => $updated_blocks) {
373
            foreach ($updated_blocks as $block_order => $block_id) {
374
                if (is_numeric($block_id)) {
375
                    // Updated block
376
                    DB::table('block')
377
                        ->where('block_id', '=', $block_id)
378
                        ->update([
379
                            'block_order' => $block_order,
380
                            'location'    => $location,
381
                        ]);
382
                } else {
383
                    // New block
384
                    DB::table('block')->insert([
385
                        'gedcom_id'   => $tree_id,
386
                        'location'    => $location,
387
                        'block_order' => $block_order,
388
                        'module_name' => $block_id,
389
                    ]);
390
                }
391
            }
392
        }
393
    }
394
395
    /**
396
     * Take a list of block names, and return block (module) objects.
397
     *
398
     * @param Collection<string>                      $blocks
399
     * @param Collection<string,ModuleBlockInterface> $active_blocks
400
     *
401
     * @return Collection<string,ModuleBlockInterface>
402
     */
403
    private function filterActiveBlocks(Collection $blocks, Collection $active_blocks): Collection
404
    {
405
        return $blocks->map(static function (string $block_name) use ($active_blocks): ?ModuleBlockInterface {
406
            return $active_blocks->filter(static function (ModuleInterface $block) use ($block_name): bool {
407
                return $block->name() === $block_name;
408
            })->first();
409
        })->filter();
410
    }
411
}
412