1a25f0a04SGreg Roach<?php 23976b470SGreg Roach 3a25f0a04SGreg Roach/** 4a25f0a04SGreg Roach * webtrees: online genealogy 58fcd0d32SGreg Roach * Copyright (C) 2019 webtrees development team 6a25f0a04SGreg Roach * This program is free software: you can redistribute it and/or modify 7a25f0a04SGreg Roach * it under the terms of the GNU General Public License as published by 8a25f0a04SGreg Roach * the Free Software Foundation, either version 3 of the License, or 9a25f0a04SGreg Roach * (at your option) any later version. 10a25f0a04SGreg Roach * This program is distributed in the hope that it will be useful, 11a25f0a04SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 12a25f0a04SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13a25f0a04SGreg Roach * GNU General Public License for more details. 14a25f0a04SGreg Roach * You should have received a copy of the GNU General Public License 15a25f0a04SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>. 16a25f0a04SGreg Roach */ 17fcfa147eSGreg Roach 18e7f56f2aSGreg Roachdeclare(strict_types=1); 19e7f56f2aSGreg Roach 2084e2cf4eSGreg Roachnamespace Fisharebest\Webtrees; 21a25f0a04SGreg Roach 225afbc57aSGreg Roachuse Fisharebest\Webtrees\Functions\FunctionsImport; 235afbc57aSGreg Roachuse Fisharebest\Webtrees\Services\TreeService; 24e5a6b4d4SGreg Roachuse Fisharebest\Webtrees\Services\UserService; 253cfcc809SGreg Roachuse InvalidArgumentException; 263976b470SGreg Roach 2794026f20SGreg Roachuse function stream_get_contents; 2894026f20SGreg Roach 29a25f0a04SGreg Roach/** 30a25f0a04SGreg Roach * Test harness for the class Tree 31a25f0a04SGreg Roach */ 32e5a6b4d4SGreg Roachclass TreeTest extends TestCase 33c1010edaSGreg Roach{ 34126654d7SGreg Roach protected static $uses_database = true; 35126654d7SGreg Roach 36a25f0a04SGreg Roach /** 37126654d7SGreg Roach * @covers \Fisharebest\Webtrees\Tree::__construct 38126654d7SGreg Roach * @covers \Fisharebest\Webtrees\Tree::id 39126654d7SGreg Roach * @covers \Fisharebest\Webtrees\Tree::name 40126654d7SGreg Roach * @covers \Fisharebest\Webtrees\Tree::title 4152348eb8SGreg Roach * @return void 42a25f0a04SGreg Roach */ 43126654d7SGreg Roach public function testConstructor(): void 44c1010edaSGreg Roach { 451e653452SGreg Roach $tree_service = new TreeService(); 461e653452SGreg Roach $tree = $tree_service->create('name', 'title'); 47126654d7SGreg Roach 481e653452SGreg Roach $this->assertSame('name', $tree->name()); 491e653452SGreg Roach $this->assertSame('title', $tree->title()); 50126654d7SGreg Roach } 51126654d7SGreg Roach 52126654d7SGreg Roach /** 53126654d7SGreg Roach * @covers \Fisharebest\Webtrees\Tree::getPreference 54126654d7SGreg Roach * @covers \Fisharebest\Webtrees\Tree::setPreference 55126654d7SGreg Roach * @return void 56126654d7SGreg Roach */ 57af343084SGreg Roach public function testTreePreferences(): void 58126654d7SGreg Roach { 591e653452SGreg Roach $tree_service = new TreeService(); 601e653452SGreg Roach $tree = $tree_service->create('name', 'title'); 61126654d7SGreg Roach 62126654d7SGreg Roach $pref = $tree->getPreference('foo', 'default'); 63126654d7SGreg Roach $this->assertSame('default', $pref); 64126654d7SGreg Roach 65126654d7SGreg Roach $tree->setPreference('foo', 'bar'); 66126654d7SGreg Roach $pref = $tree->getPreference('foo', 'default'); 67126654d7SGreg Roach $this->assertSame('bar', $pref); 68a25f0a04SGreg Roach } 69af343084SGreg Roach 70af343084SGreg Roach /** 71af343084SGreg Roach * @covers \Fisharebest\Webtrees\Tree::getUserPreference 72000dd58cSGreg Roach * @covers \Fisharebest\Webtrees\Tree::setUserPreference 73af343084SGreg Roach * @return void 74af343084SGreg Roach */ 75af343084SGreg Roach public function testUserTreePreferences(): void 76af343084SGreg Roach { 77e5a6b4d4SGreg Roach $user_service = new UserService(); 781e653452SGreg Roach $tree_service = new TreeService(); 791e653452SGreg Roach $tree = $tree_service->create('name', 'title'); 80e5a6b4d4SGreg Roach $user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 81af343084SGreg Roach 82af343084SGreg Roach $pref = $tree->getUserPreference($user, 'foo', 'default'); 83af343084SGreg Roach $this->assertSame('default', $pref); 84af343084SGreg Roach 85af343084SGreg Roach $tree->setUserPreference($user, 'foo', 'bar'); 86af343084SGreg Roach $pref = $tree->getUserPreference($user, 'foo', 'default'); 87af343084SGreg Roach $this->assertSame('bar', $pref); 88af343084SGreg Roach } 89963fbaeeSGreg Roach 90963fbaeeSGreg Roach /** 917d76853dSGreg Roach * @covers \Fisharebest\Webtrees\Tree::getNewXref 927d76853dSGreg Roach * @return void 937d76853dSGreg Roach */ 947d76853dSGreg Roach public function testGetNewXref(): void 957d76853dSGreg Roach { 965afbc57aSGreg Roach $tree_service = new TreeService(); 975afbc57aSGreg Roach $tree = $tree_service->create('tree-name', 'Tree title'); 987d76853dSGreg Roach 995afbc57aSGreg Roach // New trees have an individual X1. 1007d76853dSGreg Roach $this->assertSame('X2', $tree->getNewXref()); 1017d76853dSGreg Roach $this->assertSame('X3', $tree->getNewXref()); 1027d76853dSGreg Roach $this->assertSame('X4', $tree->getNewXref()); 1037d76853dSGreg Roach $this->assertSame('X5', $tree->getNewXref()); 1047d76853dSGreg Roach } 1057d76853dSGreg Roach 1067d76853dSGreg Roach /** 1077d76853dSGreg Roach * @covers \Fisharebest\Webtrees\Tree::createIndividual 1087d76853dSGreg Roach * @return void 1097d76853dSGreg Roach */ 1107d76853dSGreg Roach public function testCreateInvalidIndividual(): void 1117d76853dSGreg Roach { 1123cfcc809SGreg Roach $this->expectException(InvalidArgumentException::class); 1133cfcc809SGreg Roach 114e5a6b4d4SGreg Roach $user_service = new UserService(); 1151e653452SGreg Roach $tree_service = new TreeService(); 1161e653452SGreg Roach $tree = $tree_service->create('name', 'title'); 117e5a6b4d4SGreg Roach $user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 118*7c4add84SGreg Roach $user->setPreference(User::PREF_IS_ADMINISTRATOR, '1'); 1197d76853dSGreg Roach Auth::login($user); 1207d76853dSGreg Roach 1217d76853dSGreg Roach $tree->createIndividual("0 @@ FOO\n1 SEX U"); 1227d76853dSGreg Roach } 1237d76853dSGreg Roach 1247d76853dSGreg Roach /** 125963fbaeeSGreg Roach * @covers \Fisharebest\Webtrees\Tree::createIndividual 126963fbaeeSGreg Roach * @return void 127963fbaeeSGreg Roach */ 128963fbaeeSGreg Roach public function testCreateIndividual(): void 129963fbaeeSGreg Roach { 130e5a6b4d4SGreg Roach $user_service = new UserService(); 1311e653452SGreg Roach $tree_service = new TreeService(); 1321e653452SGreg Roach $tree = $tree_service->create('name', 'title'); 133e5a6b4d4SGreg Roach $user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 134*7c4add84SGreg Roach $user->setPreference(User::PREF_IS_ADMINISTRATOR, '1'); 135963fbaeeSGreg Roach Auth::login($user); 136963fbaeeSGreg Roach 1378f9b0fb2SGreg Roach $record = $tree->createIndividual("0 @@ INDI\n1 SEX F\n1 NAME Foo /Bar/"); 1388f9b0fb2SGreg Roach $this->assertTrue($record->isPendingAddition()); 139963fbaeeSGreg Roach 140*7c4add84SGreg Roach $user->setPreference(User::PREF_AUTO_ACCEPT_EDITS, '1'); 1418f9b0fb2SGreg Roach $record = $tree->createIndividual("0 @@ INDI\n1 SEX F\n1 NAME Foo /Bar/"); 1428f9b0fb2SGreg Roach $this->assertFalse($record->isPendingAddition()); 1438f9b0fb2SGreg Roach } 144963fbaeeSGreg Roach 1458f9b0fb2SGreg Roach /** 1467d76853dSGreg Roach * @covers \Fisharebest\Webtrees\Tree::createFamily 1478f9b0fb2SGreg Roach * @return void 1488f9b0fb2SGreg Roach */ 1497d76853dSGreg Roach public function testCreateInvalidFamily(): void 1508f9b0fb2SGreg Roach { 1513cfcc809SGreg Roach $this->expectException(InvalidArgumentException::class); 1523cfcc809SGreg Roach 153e5a6b4d4SGreg Roach $user_service = new UserService(); 1541e653452SGreg Roach $tree_service = new TreeService(); 1551e653452SGreg Roach $tree = $tree_service->create('name', 'title'); 156e5a6b4d4SGreg Roach $user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 157*7c4add84SGreg Roach $user->setPreference(User::PREF_IS_ADMINISTRATOR, '1'); 1587d76853dSGreg Roach Auth::login($user); 159963fbaeeSGreg Roach 1607d76853dSGreg Roach $tree->createFamily("0 @@ FOO\n1 MARR Y"); 1618f9b0fb2SGreg Roach } 162963fbaeeSGreg Roach 1638f9b0fb2SGreg Roach /** 1648f9b0fb2SGreg Roach * @covers \Fisharebest\Webtrees\Tree::createFamily 1658f9b0fb2SGreg Roach * @return void 1668f9b0fb2SGreg Roach */ 1678f9b0fb2SGreg Roach public function testCreateFamily(): void 1688f9b0fb2SGreg Roach { 169e5a6b4d4SGreg Roach $user_service = new UserService(); 1701e653452SGreg Roach $tree_service = new TreeService(); 1711e653452SGreg Roach $tree = $tree_service->create('name', 'title'); 172e5a6b4d4SGreg Roach $user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 173*7c4add84SGreg Roach $user->setPreference(User::PREF_IS_ADMINISTRATOR, '1'); 1748f9b0fb2SGreg Roach Auth::login($user); 1758f9b0fb2SGreg Roach 1768f9b0fb2SGreg Roach $record = $tree->createFamily("0 @@ FAM\n1 MARR Y"); 1778f9b0fb2SGreg Roach $this->assertTrue($record->isPendingAddition()); 1788f9b0fb2SGreg Roach 179*7c4add84SGreg Roach $user->setPreference(User::PREF_AUTO_ACCEPT_EDITS, '1'); 1808f9b0fb2SGreg Roach $record = $tree->createFamily("0 @@ FAM\n1 MARR Y"); 1818f9b0fb2SGreg Roach $this->assertFalse($record->isPendingAddition()); 1828f9b0fb2SGreg Roach } 1838f9b0fb2SGreg Roach 1848f9b0fb2SGreg Roach /** 1857d76853dSGreg Roach * @covers \Fisharebest\Webtrees\Tree::createMediaObject 1867d76853dSGreg Roach * @return void 1877d76853dSGreg Roach */ 1887d76853dSGreg Roach public function testCreateInvalidMediaObject(): void 1897d76853dSGreg Roach { 1903cfcc809SGreg Roach $this->expectException(InvalidArgumentException::class); 1913cfcc809SGreg Roach 192e5a6b4d4SGreg Roach $user_service = new UserService(); 1931e653452SGreg Roach $tree_service = new TreeService(); 1941e653452SGreg Roach $tree = $tree_service->create('name', 'title'); 195e5a6b4d4SGreg Roach $user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 196*7c4add84SGreg Roach $user->setPreference(User::PREF_IS_ADMINISTRATOR, '1'); 1977d76853dSGreg Roach Auth::login($user); 1987d76853dSGreg Roach 1997d76853dSGreg Roach $tree->createMediaObject("0 @@ FOO\n1 MARR Y"); 2007d76853dSGreg Roach } 2017d76853dSGreg Roach 2027d76853dSGreg Roach /** 2037d76853dSGreg Roach * @covers \Fisharebest\Webtrees\Tree::createMediaObject 2047d76853dSGreg Roach * @return void 2057d76853dSGreg Roach */ 2067d76853dSGreg Roach public function testCreateMediaObject(): void 2077d76853dSGreg Roach { 208e5a6b4d4SGreg Roach $user_service = new UserService(); 2091e653452SGreg Roach $tree_service = new TreeService(); 2101e653452SGreg Roach $tree = $tree_service->create('name', 'title'); 211e5a6b4d4SGreg Roach $user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 212*7c4add84SGreg Roach $user->setPreference(User::PREF_IS_ADMINISTRATOR, '1'); 2137d76853dSGreg Roach Auth::login($user); 2147d76853dSGreg Roach 2157d76853dSGreg Roach $record = $tree->createMediaObject("0 @@ OBJE\n1 FILE foo.jpeg"); 2167d76853dSGreg Roach $this->assertTrue($record->isPendingAddition()); 2177d76853dSGreg Roach 218*7c4add84SGreg Roach $user->setPreference(User::PREF_AUTO_ACCEPT_EDITS, '1'); 2197d76853dSGreg Roach $record = $tree->createMediaObject("0 @@ OBJE\n1 FILE foo.jpeg"); 2207d76853dSGreg Roach $this->assertFalse($record->isPendingAddition()); 2217d76853dSGreg Roach } 2227d76853dSGreg Roach 2237d76853dSGreg Roach /** 224bec87e94SGreg Roach * @covers \Fisharebest\Webtrees\Tree::createRecord 225bec87e94SGreg Roach * @return void 226bec87e94SGreg Roach */ 227bec87e94SGreg Roach public function testCreateInvalidRecord(): void 228bec87e94SGreg Roach { 2293cfcc809SGreg Roach $this->expectException(InvalidArgumentException::class); 2303cfcc809SGreg Roach 231e5a6b4d4SGreg Roach $user_service = new UserService(); 2321e653452SGreg Roach $tree_service = new TreeService(); 2331e653452SGreg Roach $tree = $tree_service->create('name', 'title'); 234e5a6b4d4SGreg Roach $user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 235*7c4add84SGreg Roach $user->setPreference(User::PREF_IS_ADMINISTRATOR, '1'); 236bec87e94SGreg Roach Auth::login($user); 237bec87e94SGreg Roach 238bec87e94SGreg Roach $tree->createRecord("0 @@FOO\n1 NOTE noted"); 239bec87e94SGreg Roach } 240bec87e94SGreg Roach 241bec87e94SGreg Roach /** 242bec87e94SGreg Roach * @covers \Fisharebest\Webtrees\Tree::createRecord 243bec87e94SGreg Roach * @return void 244bec87e94SGreg Roach */ 245bec87e94SGreg Roach public function testCreateRecord(): void 246bec87e94SGreg Roach { 247e5a6b4d4SGreg Roach $user_service = new UserService(); 2481e653452SGreg Roach $tree_service = new TreeService(); 2491e653452SGreg Roach $tree = $tree_service->create('name', 'title'); 250e5a6b4d4SGreg Roach $user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 251*7c4add84SGreg Roach $user->setPreference(User::PREF_IS_ADMINISTRATOR, '1'); 252bec87e94SGreg Roach Auth::login($user); 253bec87e94SGreg Roach 254bec87e94SGreg Roach $record = $tree->createRecord("0 @@ FOO\n1 NOTE noted"); 255bec87e94SGreg Roach $this->assertTrue($record->isPendingAddition()); 256bec87e94SGreg Roach 257*7c4add84SGreg Roach $user->setPreference(User::PREF_AUTO_ACCEPT_EDITS, '1'); 258bec87e94SGreg Roach $record = $tree->createRecord("0 @@ FOO\n1 NOTE noted"); 259bec87e94SGreg Roach $this->assertFalse($record->isPendingAddition()); 260bec87e94SGreg Roach } 261bec87e94SGreg Roach 262bec87e94SGreg Roach /** 2638f9b0fb2SGreg Roach * @covers \Fisharebest\Webtrees\Tree::significantIndividual 2648f9b0fb2SGreg Roach * @return void 2658f9b0fb2SGreg Roach */ 2668f9b0fb2SGreg Roach public function testSignificantIndividual(): void 2678f9b0fb2SGreg Roach { 268e5a6b4d4SGreg Roach $user_service = new UserService(); 2691e653452SGreg Roach $tree_service = new TreeService(); 2701e653452SGreg Roach $tree = $tree_service->create('name', 'title'); 271e5a6b4d4SGreg Roach $user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 272*7c4add84SGreg Roach $user->setPreference(User::PREF_AUTO_ACCEPT_EDITS, '1'); 2738f9b0fb2SGreg Roach Auth::login($user); 2748f9b0fb2SGreg Roach 2755afbc57aSGreg Roach // Delete the tree's default individual. 2765afbc57aSGreg Roach FunctionsImport::updateRecord('0 @X1@ INDI', $tree, true); 2775afbc57aSGreg Roach 2788f9b0fb2SGreg Roach // No individuals in tree? Dummy individual 2798f9b0fb2SGreg Roach $this->assertSame('I', $tree->significantIndividual($user)->xref()); 2808f9b0fb2SGreg Roach 2818f9b0fb2SGreg Roach $record1 = $tree->createIndividual("0 @@ INDI\n1 SEX F\n1 NAME Foo /Bar/"); 2828f9b0fb2SGreg Roach $record2 = $tree->createIndividual("0 @@ INDI\n1 SEX F\n1 NAME Foo /Bar/"); 2838f9b0fb2SGreg Roach $record3 = $tree->createIndividual("0 @@ INDI\n1 SEX F\n1 NAME Foo /Bar/"); 2848f9b0fb2SGreg Roach $record4 = $tree->createIndividual("0 @@ INDI\n1 SEX F\n1 NAME Foo /Bar/"); 2858f9b0fb2SGreg Roach 2868f9b0fb2SGreg Roach // Individuals exist? First one (lowest XREF). 2878f9b0fb2SGreg Roach $this->assertSame($record1->xref(), $tree->significantIndividual($user)->xref()); 2888f9b0fb2SGreg Roach 2898f9b0fb2SGreg Roach // Preference for tree? 2908f9b0fb2SGreg Roach $tree->setPreference('PEDIGREE_ROOT_ID', $record2->xref()); 2918f9b0fb2SGreg Roach $this->assertSame($record2->xref(), $tree->significantIndividual($user)->xref()); 2928f9b0fb2SGreg Roach 2938f9b0fb2SGreg Roach // User preference 294*7c4add84SGreg Roach $tree->setUserPreference($user, User::PREF_TREE_ACCOUNT_XREF, $record3->xref()); 2958f9b0fb2SGreg Roach $this->assertSame($record3->xref(), $tree->significantIndividual($user)->xref()); 2968f9b0fb2SGreg Roach 2978f9b0fb2SGreg Roach // User record 298*7c4add84SGreg Roach $tree->setUserPreference($user, User::PREF_TREE_DEFAULT_XREF, $record4->xref()); 2998f9b0fb2SGreg Roach $this->assertSame($record4->xref(), $tree->significantIndividual($user)->xref()); 300963fbaeeSGreg Roach } 301a7890280SGreg Roach 302a7890280SGreg Roach /** 303a7890280SGreg Roach * @covers \Fisharebest\Webtrees\Tree::importGedcomFile 304a7890280SGreg Roach * @covers \Fisharebest\Webtrees\Tree::deleteGenealogyData 305a7890280SGreg Roach * @return void 306a7890280SGreg Roach */ 307a7890280SGreg Roach public function testImportAndDeleteGedcomFile(): void 308a7890280SGreg Roach { 3095afbc57aSGreg Roach $tree_service = new TreeService(); 310a7890280SGreg Roach $tree = $this->importTree('demo.ged'); 3111e653452SGreg Roach $this->assertNotNull($tree_service->all()->get('demo.ged')); 312432ff88aSGreg Roach Site::setPreference('DEFAULT_GEDCOM', $tree->name()); 313a7890280SGreg Roach 3145afbc57aSGreg Roach $tree_service->delete($tree); 315432ff88aSGreg Roach 3161e653452SGreg Roach $this->assertNull($tree_service->all()->get('demo.ged')); 317432ff88aSGreg Roach $this->assertSame('', Site::getPreference('DEFAULT_GEDCOM')); 318a7890280SGreg Roach } 31915a3f100SGreg Roach 32015a3f100SGreg Roach /** 32115a3f100SGreg Roach * @covers \Fisharebest\Webtrees\Tree::hasPendingEdit 32215a3f100SGreg Roach * @return void 32315a3f100SGreg Roach */ 32415a3f100SGreg Roach public function testHasPendingEdits(): void 32515a3f100SGreg Roach { 326e5a6b4d4SGreg Roach $user_service = new UserService(); 32715a3f100SGreg Roach $tree = $this->importTree('demo.ged'); 328e5a6b4d4SGreg Roach $user = $user_service->create('admin', 'Administrator', 'admin@example.com', 'secret'); 329*7c4add84SGreg Roach $user->setPreference(User::PREF_IS_ADMINISTRATOR, '1'); 33015a3f100SGreg Roach Auth::login($user); 33115a3f100SGreg Roach 332*7c4add84SGreg Roach $user->setPreference(User::PREF_AUTO_ACCEPT_EDITS, '1'); 33315a3f100SGreg Roach $tree->createIndividual("0 @@ INDI\n1 SEX F\n1 NAME Foo /Bar/"); 33415a3f100SGreg Roach $this->assertFalse($tree->hasPendingEdit()); 33515a3f100SGreg Roach 336*7c4add84SGreg Roach $user->setPreference(User::PREF_AUTO_ACCEPT_EDITS, ''); 33715a3f100SGreg Roach $tree->createIndividual("0 @@ INDI\n1 SEX F\n1 NAME Foo /Bar/"); 33815a3f100SGreg Roach $this->assertTrue($tree->hasPendingEdit()); 33915a3f100SGreg Roach } 34094026f20SGreg Roach 34194026f20SGreg Roach /** 34294026f20SGreg Roach * @covers \Fisharebest\Webtrees\Tree::exportGedcom 34394026f20SGreg Roach * @return void 34494026f20SGreg Roach */ 34594026f20SGreg Roach public function testExportGedcom(): void 34694026f20SGreg Roach { 34794026f20SGreg Roach $tree = $this->importTree('demo.ged'); 34894026f20SGreg Roach 349803193feSGreg Roach $fp = fopen('php://memory', 'wb'); 35094026f20SGreg Roach 35194026f20SGreg Roach $tree->exportGedcom($fp); 35294026f20SGreg Roach 35394026f20SGreg Roach rewind($fp); 35494026f20SGreg Roach 35594026f20SGreg Roach $original = file_get_contents(__DIR__ . '/../data/demo.ged'); 35694026f20SGreg Roach $export = stream_get_contents($fp); 35794026f20SGreg Roach 35894026f20SGreg Roach // The date and time in the HEAD record will be different. 35994026f20SGreg Roach $original = preg_replace('/\n1 DATE .. ... ..../', '', $original, 1); 36094026f20SGreg Roach $export = preg_replace('/\n1 DATE .. ... ..../', '', $export, 1); 36194026f20SGreg Roach $original = preg_replace('/\n2 TIME ..:..:../', '', $original, 1); 36294026f20SGreg Roach $export = preg_replace('/\n2 TIME ..:..:../', '', $export, 1); 36394026f20SGreg Roach 36494026f20SGreg Roach $this->assertSame($original, $export); 36594026f20SGreg Roach } 366a25f0a04SGreg Roach} 367