18e0e1b25SGreg Roach<?php 28e0e1b25SGreg Roach 38e0e1b25SGreg Roach/** 48e0e1b25SGreg Roach * webtrees: online genealogy 5d11be702SGreg Roach * Copyright (C) 2023 webtrees development team 68e0e1b25SGreg Roach * This program is free software: you can redistribute it and/or modify 78e0e1b25SGreg Roach * it under the terms of the GNU General Public License as published by 88e0e1b25SGreg Roach * the Free Software Foundation, either version 3 of the License, or 98e0e1b25SGreg Roach * (at your option) any later version. 108e0e1b25SGreg Roach * This program is distributed in the hope that it will be useful, 118e0e1b25SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 128e0e1b25SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 138e0e1b25SGreg Roach * GNU General Public License for more details. 148e0e1b25SGreg Roach * You should have received a copy of the GNU General Public License 1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>. 168e0e1b25SGreg Roach */ 178e0e1b25SGreg Roach 188e0e1b25SGreg Roachdeclare(strict_types=1); 198e0e1b25SGreg Roach 208e0e1b25SGreg Roachnamespace Fisharebest\Webtrees\Services; 218e0e1b25SGreg Roach 228e0e1b25SGreg Roachuse Fisharebest\Webtrees\Auth; 238e0e1b25SGreg Roachuse Fisharebest\Webtrees\Contracts\UserInterface; 246f4ec3caSGreg Roachuse Fisharebest\Webtrees\DB; 2581b729d3SGreg Roachuse Fisharebest\Webtrees\Http\Exceptions\HttpNotFoundException; 268e0e1b25SGreg Roachuse Fisharebest\Webtrees\Module\ModuleBlockInterface; 278e0e1b25SGreg Roachuse Fisharebest\Webtrees\Module\ModuleInterface; 288e0e1b25SGreg Roachuse Fisharebest\Webtrees\Tree; 29b55cbc6bSGreg Roachuse Fisharebest\Webtrees\Validator; 308e0e1b25SGreg Roachuse Illuminate\Support\Collection; 318e0e1b25SGreg Roachuse Psr\Http\Message\ServerRequestInterface; 328e0e1b25SGreg Roach 338e0e1b25SGreg Roachuse function is_numeric; 34f70bcff5SGreg Roachuse function is_object; 358e0e1b25SGreg Roach 368e0e1b25SGreg Roach/** 378e0e1b25SGreg Roach * Logic and content for the home-page blocks. 388e0e1b25SGreg Roach */ 398e0e1b25SGreg Roachclass HomePageService 408e0e1b25SGreg Roach{ 41c4943cffSGreg Roach private ModuleService $module_service; 428e0e1b25SGreg Roach 438e0e1b25SGreg Roach /** 448e0e1b25SGreg Roach * @param ModuleService $module_service 458e0e1b25SGreg Roach */ 468e0e1b25SGreg Roach public function __construct(ModuleService $module_service) 478e0e1b25SGreg Roach { 488e0e1b25SGreg Roach $this->module_service = $module_service; 498e0e1b25SGreg Roach } 508e0e1b25SGreg Roach 518e0e1b25SGreg Roach /** 5224c0163eSGreg Roach * Load a tree block. 538e0e1b25SGreg Roach * 548e0e1b25SGreg Roach * @param ServerRequestInterface $request 558e0e1b25SGreg Roach * 568e0e1b25SGreg Roach * @return ModuleBlockInterface 578e0e1b25SGreg Roach */ 5824c0163eSGreg Roach public function treeBlock(ServerRequestInterface $request): ModuleBlockInterface 598e0e1b25SGreg Roach { 60b55cbc6bSGreg Roach $tree = Validator::attributes($request)->tree(); 61b55cbc6bSGreg Roach $block_id = Validator::attributes($request)->integer('block_id'); 628e0e1b25SGreg Roach 638e0e1b25SGreg Roach $block = DB::table('block') 648e0e1b25SGreg Roach ->where('block_id', '=', $block_id) 658e0e1b25SGreg Roach ->where('gedcom_id', '=', $tree->id()) 668e0e1b25SGreg Roach ->whereNull('user_id') 678e0e1b25SGreg Roach ->first(); 688e0e1b25SGreg Roach 69f70bcff5SGreg Roach if (is_object($block)) { 708e0e1b25SGreg Roach $module = $this->module_service->findByName($block->module_name); 718e0e1b25SGreg Roach 7224c0163eSGreg Roach if ($module instanceof ModuleBlockInterface) { 738e0e1b25SGreg Roach return $module; 748e0e1b25SGreg Roach } 7524c0163eSGreg Roach } 7624c0163eSGreg Roach 7724c0163eSGreg Roach throw new HttpNotFoundException(); 7824c0163eSGreg Roach } 798e0e1b25SGreg Roach 808e0e1b25SGreg Roach /** 8124c0163eSGreg Roach * Load a user block. 828e0e1b25SGreg Roach * 838e0e1b25SGreg Roach * @param ServerRequestInterface $request 848e0e1b25SGreg Roach * @param UserInterface $user 858e0e1b25SGreg Roach * 868e0e1b25SGreg Roach * @return ModuleBlockInterface 878e0e1b25SGreg Roach */ 888e0e1b25SGreg Roach public function userBlock(ServerRequestInterface $request, UserInterface $user): ModuleBlockInterface 898e0e1b25SGreg Roach { 90b55cbc6bSGreg Roach $block_id = Validator::attributes($request)->integer('block_id'); 918e0e1b25SGreg Roach 928e0e1b25SGreg Roach $block = DB::table('block') 938e0e1b25SGreg Roach ->where('block_id', '=', $block_id) 948e0e1b25SGreg Roach ->where('user_id', '=', $user->id()) 958e0e1b25SGreg Roach ->whereNull('gedcom_id') 968e0e1b25SGreg Roach ->first(); 978e0e1b25SGreg Roach 98f70bcff5SGreg Roach if (is_object($block)) { 998e0e1b25SGreg Roach $module = $this->module_service->findByName($block->module_name); 1008e0e1b25SGreg Roach 10124c0163eSGreg Roach if ($module instanceof ModuleBlockInterface) { 1028e0e1b25SGreg Roach return $module; 1038e0e1b25SGreg Roach } 10424c0163eSGreg Roach } 10524c0163eSGreg Roach 10624c0163eSGreg Roach throw new HttpNotFoundException(); 10724c0163eSGreg Roach } 1088e0e1b25SGreg Roach 1098e0e1b25SGreg Roach /** 1108e0e1b25SGreg Roach * Get a specific block. 1118e0e1b25SGreg Roach * 1128e0e1b25SGreg Roach * @param Tree $tree 1138e0e1b25SGreg Roach * @param int $block_id 1148e0e1b25SGreg Roach * 1158e0e1b25SGreg Roach * @return ModuleBlockInterface 1168e0e1b25SGreg Roach */ 1178e0e1b25SGreg Roach public function getBlockModule(Tree $tree, int $block_id): ModuleBlockInterface 1188e0e1b25SGreg Roach { 1198e0e1b25SGreg Roach $active_blocks = $this->module_service->findByComponent(ModuleBlockInterface::class, $tree, Auth::user()); 1208e0e1b25SGreg Roach 1218e0e1b25SGreg Roach $module_name = DB::table('block') 1228e0e1b25SGreg Roach ->where('block_id', '=', $block_id) 1238e0e1b25SGreg Roach ->value('module_name'); 1248e0e1b25SGreg Roach 125f25fc0f9SGreg Roach $block = $active_blocks->first(static fn (ModuleInterface $module): bool => $module->name() === $module_name); 1268e0e1b25SGreg Roach 1278e0e1b25SGreg Roach if ($block instanceof ModuleBlockInterface) { 1288e0e1b25SGreg Roach return $block; 1298e0e1b25SGreg Roach } 1308e0e1b25SGreg Roach 1318e0e1b25SGreg Roach throw new HttpNotFoundException('Block not found'); 1328e0e1b25SGreg Roach } 1338e0e1b25SGreg Roach 1348e0e1b25SGreg Roach /** 1358e0e1b25SGreg Roach * Get all the available blocks for a tree page. 1368e0e1b25SGreg Roach * 137f6924bc8SGreg Roach * @param Tree $tree 138f6924bc8SGreg Roach * @param UserInterface $user 139f6924bc8SGreg Roach * 1408e0e1b25SGreg Roach * @return Collection<string,ModuleBlockInterface> 1418e0e1b25SGreg Roach */ 142f6924bc8SGreg Roach public function availableTreeBlocks(Tree $tree, UserInterface $user): Collection 1438e0e1b25SGreg Roach { 144f6924bc8SGreg Roach return $this->module_service->findByComponent(ModuleBlockInterface::class, $tree, $user) 145f25fc0f9SGreg Roach ->filter(static fn (ModuleBlockInterface $block): bool => $block->isTreeBlock()) 146f25fc0f9SGreg Roach ->mapWithKeys(static fn (ModuleBlockInterface $block): array => [$block->name() => $block]); 1478e0e1b25SGreg Roach } 1488e0e1b25SGreg Roach 1498e0e1b25SGreg Roach /** 1508e0e1b25SGreg Roach * Get all the available blocks for a user page. 1518e0e1b25SGreg Roach * 152f6924bc8SGreg Roach * @param Tree $tree 153f6924bc8SGreg Roach * @param UserInterface $user 154f6924bc8SGreg Roach * 1558e0e1b25SGreg Roach * @return Collection<string,ModuleBlockInterface> 1568e0e1b25SGreg Roach */ 157f6924bc8SGreg Roach public function availableUserBlocks(Tree $tree, UserInterface $user): Collection 1588e0e1b25SGreg Roach { 159f6924bc8SGreg Roach return $this->module_service->findByComponent(ModuleBlockInterface::class, $tree, $user) 160f25fc0f9SGreg Roach ->filter(static fn (ModuleBlockInterface $block): bool => $block->isUserBlock()) 161f25fc0f9SGreg Roach ->mapWithKeys(static fn (ModuleBlockInterface $block): array => [$block->name() => $block]); 1628e0e1b25SGreg Roach } 1638e0e1b25SGreg Roach 1648e0e1b25SGreg Roach /** 1658e0e1b25SGreg Roach * Get the blocks for a specified tree. 1668e0e1b25SGreg Roach * 167f6924bc8SGreg Roach * @param Tree $tree 168f6924bc8SGreg Roach * @param UserInterface $user 1698e0e1b25SGreg Roach * @param string $location "main" or "side" 1708e0e1b25SGreg Roach * 1717c2c99faSGreg Roach * @return Collection<int,ModuleBlockInterface> 1728e0e1b25SGreg Roach */ 173f6924bc8SGreg Roach public function treeBlocks(Tree $tree, UserInterface $user, string $location): Collection 1748e0e1b25SGreg Roach { 1758e0e1b25SGreg Roach $rows = DB::table('block') 176f6924bc8SGreg Roach ->where('gedcom_id', '=', $tree->id()) 1778e0e1b25SGreg Roach ->where('location', '=', $location) 1788e0e1b25SGreg Roach ->orderBy('block_order') 1798e0e1b25SGreg Roach ->pluck('module_name', 'block_id'); 1808e0e1b25SGreg Roach 181f6924bc8SGreg Roach return $this->filterActiveBlocks($rows, $this->availableTreeBlocks($tree, $user)); 1828e0e1b25SGreg Roach } 1838e0e1b25SGreg Roach 1848e0e1b25SGreg Roach /** 1858e0e1b25SGreg Roach * Make sure that default blocks exist for a tree. 1868e0e1b25SGreg Roach * 1878e0e1b25SGreg Roach * @return void 1888e0e1b25SGreg Roach */ 1898e0e1b25SGreg Roach public function checkDefaultTreeBlocksExist(): void 1908e0e1b25SGreg Roach { 1918e0e1b25SGreg Roach $has_blocks = DB::table('block') 1928e0e1b25SGreg Roach ->where('gedcom_id', '=', -1) 1938e0e1b25SGreg Roach ->exists(); 1948e0e1b25SGreg Roach 1958e0e1b25SGreg Roach // No default settings? Create them. 1968e0e1b25SGreg Roach if (!$has_blocks) { 1978e0e1b25SGreg Roach foreach ([ModuleBlockInterface::MAIN_BLOCKS, ModuleBlockInterface::SIDE_BLOCKS] as $location) { 1988e0e1b25SGreg Roach foreach (ModuleBlockInterface::DEFAULT_TREE_PAGE_BLOCKS[$location] as $block_order => $class) { 1993cf189cfSGreg Roach $module = $this->module_service->findByInterface($class)->first(); 2008e0e1b25SGreg Roach 2013cf189cfSGreg Roach if ($module instanceof ModuleInterface) { 2028e0e1b25SGreg Roach DB::table('block')->insert([ 2038e0e1b25SGreg Roach 'gedcom_id' => -1, 2048e0e1b25SGreg Roach 'location' => $location, 2058e0e1b25SGreg Roach 'block_order' => $block_order, 2063cf189cfSGreg Roach 'module_name' => $module->name(), 2078e0e1b25SGreg Roach ]); 2088e0e1b25SGreg Roach } 2098e0e1b25SGreg Roach } 2108e0e1b25SGreg Roach } 2118e0e1b25SGreg Roach } 2123cf189cfSGreg Roach } 2138e0e1b25SGreg Roach 2148e0e1b25SGreg Roach /** 2158e0e1b25SGreg Roach * Get the blocks for a specified user. 2168e0e1b25SGreg Roach * 217f6924bc8SGreg Roach * @param Tree $tree 218f6924bc8SGreg Roach * @param UserInterface $user 2198e0e1b25SGreg Roach * @param string $location "main" or "side" 2208e0e1b25SGreg Roach * 2217c2c99faSGreg Roach * @return Collection<int,ModuleBlockInterface> 2228e0e1b25SGreg Roach */ 223f6924bc8SGreg Roach public function userBlocks(Tree $tree, UserInterface $user, string $location): Collection 2248e0e1b25SGreg Roach { 2258e0e1b25SGreg Roach $rows = DB::table('block') 226f6924bc8SGreg Roach ->where('user_id', '=', $user->id()) 2278e0e1b25SGreg Roach ->where('location', '=', $location) 2288e0e1b25SGreg Roach ->orderBy('block_order') 2298e0e1b25SGreg Roach ->pluck('module_name', 'block_id'); 2308e0e1b25SGreg Roach 231f6924bc8SGreg Roach return $this->filterActiveBlocks($rows, $this->availableUserBlocks($tree, $user)); 2328e0e1b25SGreg Roach } 2338e0e1b25SGreg Roach 2348e0e1b25SGreg Roach /** 2358e0e1b25SGreg Roach * Make sure that default blocks exist for a user. 2368e0e1b25SGreg Roach * 2378e0e1b25SGreg Roach * @return void 2388e0e1b25SGreg Roach */ 2398e0e1b25SGreg Roach public function checkDefaultUserBlocksExist(): void 2408e0e1b25SGreg Roach { 2418e0e1b25SGreg Roach $has_blocks = DB::table('block') 2428e0e1b25SGreg Roach ->where('user_id', '=', -1) 2438e0e1b25SGreg Roach ->exists(); 2448e0e1b25SGreg Roach 2458e0e1b25SGreg Roach // No default settings? Create them. 2468e0e1b25SGreg Roach if (!$has_blocks) { 2478e0e1b25SGreg Roach foreach ([ModuleBlockInterface::MAIN_BLOCKS, ModuleBlockInterface::SIDE_BLOCKS] as $location) { 2488e0e1b25SGreg Roach foreach (ModuleBlockInterface::DEFAULT_USER_PAGE_BLOCKS[$location] as $block_order => $class) { 24923b84b22SGreg Roach $module = $this->module_service->findByInterface($class)->first(); 2508e0e1b25SGreg Roach 25123b84b22SGreg Roach if ($module instanceof ModuleBlockInterface) { 2528e0e1b25SGreg Roach DB::table('block')->insert([ 2538e0e1b25SGreg Roach 'user_id' => -1, 2548e0e1b25SGreg Roach 'location' => $location, 2558e0e1b25SGreg Roach 'block_order' => $block_order, 25623b84b22SGreg Roach 'module_name' => $module->name(), 2578e0e1b25SGreg Roach ]); 2588e0e1b25SGreg Roach } 2598e0e1b25SGreg Roach } 2608e0e1b25SGreg Roach } 2618e0e1b25SGreg Roach } 26223b84b22SGreg Roach } 2638e0e1b25SGreg Roach 2648e0e1b25SGreg Roach /** 2658e0e1b25SGreg Roach * Save the updated blocks for a user. 2668e0e1b25SGreg Roach * 2678e0e1b25SGreg Roach * @param int $user_id 26836779af1SGreg Roach * @param Collection<int,string> $main_block_ids 26936779af1SGreg Roach * @param Collection<int,string> $side_block_ids 2708e0e1b25SGreg Roach * 2718e0e1b25SGreg Roach * @return void 2728e0e1b25SGreg Roach */ 2738e0e1b25SGreg Roach public function updateUserBlocks(int $user_id, Collection $main_block_ids, Collection $side_block_ids): void 2748e0e1b25SGreg Roach { 2758e0e1b25SGreg Roach $existing_block_ids = DB::table('block') 2768e0e1b25SGreg Roach ->where('user_id', '=', $user_id) 27778144954SGreg Roach ->whereIn('location', [ModuleBlockInterface::MAIN_BLOCKS, ModuleBlockInterface::SIDE_BLOCKS]) 2788e0e1b25SGreg Roach ->pluck('block_id'); 2798e0e1b25SGreg Roach 2808e0e1b25SGreg Roach // Deleted blocks 2818e0e1b25SGreg Roach foreach ($existing_block_ids as $existing_block_id) { 2828e0e1b25SGreg Roach if (!$main_block_ids->contains($existing_block_id) && !$side_block_ids->contains($existing_block_id)) { 2838e0e1b25SGreg Roach DB::table('block_setting') 2848e0e1b25SGreg Roach ->where('block_id', '=', $existing_block_id) 2858e0e1b25SGreg Roach ->delete(); 2868e0e1b25SGreg Roach 2878e0e1b25SGreg Roach DB::table('block') 2888e0e1b25SGreg Roach ->where('block_id', '=', $existing_block_id) 2898e0e1b25SGreg Roach ->delete(); 2908e0e1b25SGreg Roach } 2918e0e1b25SGreg Roach } 2928e0e1b25SGreg Roach 2938e0e1b25SGreg Roach $updates = [ 2948e0e1b25SGreg Roach ModuleBlockInterface::MAIN_BLOCKS => $main_block_ids, 2958e0e1b25SGreg Roach ModuleBlockInterface::SIDE_BLOCKS => $side_block_ids, 2968e0e1b25SGreg Roach ]; 2978e0e1b25SGreg Roach 2988e0e1b25SGreg Roach foreach ($updates as $location => $updated_blocks) { 2998e0e1b25SGreg Roach foreach ($updated_blocks as $block_order => $block_id) { 3008e0e1b25SGreg Roach if (is_numeric($block_id)) { 3018e0e1b25SGreg Roach // Updated block 3028e0e1b25SGreg Roach DB::table('block') 3038e0e1b25SGreg Roach ->where('block_id', '=', $block_id) 3048e0e1b25SGreg Roach ->update([ 3058e0e1b25SGreg Roach 'block_order' => $block_order, 3068e0e1b25SGreg Roach 'location' => $location, 3078e0e1b25SGreg Roach ]); 3088e0e1b25SGreg Roach } else { 3098e0e1b25SGreg Roach // New block 3108e0e1b25SGreg Roach DB::table('block')->insert([ 3118e0e1b25SGreg Roach 'user_id' => $user_id, 3128e0e1b25SGreg Roach 'location' => $location, 3138e0e1b25SGreg Roach 'block_order' => $block_order, 3148e0e1b25SGreg Roach 'module_name' => $block_id, 3158e0e1b25SGreg Roach ]); 3168e0e1b25SGreg Roach } 3178e0e1b25SGreg Roach } 3188e0e1b25SGreg Roach } 3198e0e1b25SGreg Roach } 3208e0e1b25SGreg Roach 3218e0e1b25SGreg Roach /** 3228e0e1b25SGreg Roach * Save the updated blocks for a tree. 3238e0e1b25SGreg Roach * 3248e0e1b25SGreg Roach * @param int $tree_id 32536779af1SGreg Roach * @param Collection<int,string> $main_block_ids 32636779af1SGreg Roach * @param Collection<int,string> $side_block_ids 3278e0e1b25SGreg Roach * 3288e0e1b25SGreg Roach * @return void 3298e0e1b25SGreg Roach */ 3308e0e1b25SGreg Roach public function updateTreeBlocks(int $tree_id, Collection $main_block_ids, Collection $side_block_ids): void 3318e0e1b25SGreg Roach { 3328e0e1b25SGreg Roach $existing_block_ids = DB::table('block') 3338e0e1b25SGreg Roach ->where('gedcom_id', '=', $tree_id) 33478144954SGreg Roach ->whereIn('location', [ModuleBlockInterface::MAIN_BLOCKS, ModuleBlockInterface::SIDE_BLOCKS]) 3358e0e1b25SGreg Roach ->pluck('block_id'); 3368e0e1b25SGreg Roach 3378e0e1b25SGreg Roach // Deleted blocks 3388e0e1b25SGreg Roach foreach ($existing_block_ids as $existing_block_id) { 3398e0e1b25SGreg Roach if (!$main_block_ids->contains($existing_block_id) && !$side_block_ids->contains($existing_block_id)) { 3408e0e1b25SGreg Roach DB::table('block_setting') 3418e0e1b25SGreg Roach ->where('block_id', '=', $existing_block_id) 3428e0e1b25SGreg Roach ->delete(); 3438e0e1b25SGreg Roach 3448e0e1b25SGreg Roach DB::table('block') 3458e0e1b25SGreg Roach ->where('block_id', '=', $existing_block_id) 3468e0e1b25SGreg Roach ->delete(); 3478e0e1b25SGreg Roach } 3488e0e1b25SGreg Roach } 3498e0e1b25SGreg Roach 3508e0e1b25SGreg Roach $updates = [ 3518e0e1b25SGreg Roach ModuleBlockInterface::MAIN_BLOCKS => $main_block_ids, 3528e0e1b25SGreg Roach ModuleBlockInterface::SIDE_BLOCKS => $side_block_ids, 3538e0e1b25SGreg Roach ]; 3548e0e1b25SGreg Roach 3558e0e1b25SGreg Roach foreach ($updates as $location => $updated_blocks) { 3568e0e1b25SGreg Roach foreach ($updated_blocks as $block_order => $block_id) { 3578e0e1b25SGreg Roach if (is_numeric($block_id)) { 3588e0e1b25SGreg Roach // Updated block 3598e0e1b25SGreg Roach DB::table('block') 3608e0e1b25SGreg Roach ->where('block_id', '=', $block_id) 3618e0e1b25SGreg Roach ->update([ 3628e0e1b25SGreg Roach 'block_order' => $block_order, 3638e0e1b25SGreg Roach 'location' => $location, 3648e0e1b25SGreg Roach ]); 3658e0e1b25SGreg Roach } else { 3668e0e1b25SGreg Roach // New block 3678e0e1b25SGreg Roach DB::table('block')->insert([ 3688e0e1b25SGreg Roach 'gedcom_id' => $tree_id, 3698e0e1b25SGreg Roach 'location' => $location, 3708e0e1b25SGreg Roach 'block_order' => $block_order, 3718e0e1b25SGreg Roach 'module_name' => $block_id, 3728e0e1b25SGreg Roach ]); 3738e0e1b25SGreg Roach } 3748e0e1b25SGreg Roach } 3758e0e1b25SGreg Roach } 3768e0e1b25SGreg Roach } 3778e0e1b25SGreg Roach 3788e0e1b25SGreg Roach /** 3798e0e1b25SGreg Roach * Take a list of block names, and return block (module) objects. 3808e0e1b25SGreg Roach * 38136779af1SGreg Roach * @param Collection<int,string> $blocks 3827c2c99faSGreg Roach * @param Collection<int,ModuleBlockInterface> $active_blocks 3838e0e1b25SGreg Roach * 3847c2c99faSGreg Roach * @return Collection<int,ModuleBlockInterface> 3858e0e1b25SGreg Roach */ 3868e0e1b25SGreg Roach private function filterActiveBlocks(Collection $blocks, Collection $active_blocks): Collection 3878e0e1b25SGreg Roach { 388*1ff45046SGreg Roach return $blocks->map(static fn (string $block_name): ModuleBlockInterface|null => $active_blocks->filter(static fn (ModuleInterface $block): bool => $block->name() === $block_name)->first()) 389*1ff45046SGreg Roach ->filter(); 3908e0e1b25SGreg Roach } 3918e0e1b25SGreg Roach} 392