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