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