diff options
| -rw-r--r-- | app/Module.php | 2 | ||||
| -rw-r--r-- | app/Tree.php | 143 | ||||
| -rw-r--r-- | tests/TestCase.php | 72 | ||||
| -rw-r--r-- | tests/feature/MigrationTest.php | 40 | ||||
| -rw-r--r-- | tests/feature/UserTest.php | 138 |
5 files changed, 260 insertions, 135 deletions
diff --git a/app/Module.php b/app/Module.php index f260e99043..a0c86fff69 100644 --- a/app/Module.php +++ b/app/Module.php @@ -510,7 +510,7 @@ class Module * * @return void */ - public static function setDefaultAccess($tree_id) + public static function setDefaultAccess(int $tree_id): void { foreach (self::getInstalledModules('disabled') as $module) { if ($module instanceof ModuleMenuInterface) { diff --git a/app/Tree.php b/app/Tree.php index 0fc313c19f..7c63f6cab1 100644 --- a/app/Tree.php +++ b/app/Tree.php @@ -59,6 +59,13 @@ class Tree /** @var string[][] Cached copy of the wt_user_gedcom_setting table. */ private $user_preferences = []; + private const RESN_PRIVACY = [ + 'none' => Auth::PRIV_PRIVATE, + 'privacy' => Auth::PRIV_USER, + 'confidential' => Auth::PRIV_NONE, + 'hidden' => Auth::PRIV_HIDE, + ]; + /** * Create a tree object. This is a private constructor - it can only * be called from Tree::getAll() to ensure proper initialisation. @@ -77,18 +84,14 @@ class Tree $this->individual_fact_privacy = []; // Load the privacy settings for this tree - $rows = Database::prepare( - "SELECT xref, tag_type, CASE resn WHEN 'none' THEN :priv_public WHEN 'privacy' THEN :priv_user WHEN 'confidential' THEN :priv_none WHEN 'hidden' THEN :priv_hide END AS resn" . - " FROM `##default_resn` WHERE gedcom_id = :tree_id" - )->execute([ - 'priv_public' => Auth::PRIV_PRIVATE, - 'priv_user' => Auth::PRIV_USER, - 'priv_none' => Auth::PRIV_NONE, - 'priv_hide' => Auth::PRIV_HIDE, - 'tree_id' => $this->id, - ])->fetchAll(); + $rows = DB::table('default_resn') + ->where('gedcom_id', '=', $this->id) + ->get(); foreach ($rows as $row) { + // Convert GEDCOM privacy restriction to a webtrees access level. + $row->resn = self::RESN_PRIVACY[$row->resn]; + if ($row->xref !== null) { if ($row->tag_type !== null) { $this->individual_fact_privacy[$row->xref][$row->tag_type] = (int) $row->resn; @@ -172,9 +175,10 @@ class Tree public function getPreference(string $setting_name, string $default = ''): string { if (empty($this->preferences)) { - $this->preferences = Database::prepare( - "SELECT setting_name, setting_value FROM `##gedcom_setting` WHERE gedcom_id = ?" - )->execute([$this->id])->fetchAssoc(); + $this->preferences = DB::table('gedcom_setting') + ->where('gedcom_id', '=', $this->id) + ->pluck('setting_value', 'setting_name') + ->all(); } return $this->preferences[$setting_name] ?? $default; @@ -191,12 +195,10 @@ class Tree public function setPreference(string $setting_name, string $setting_value): Tree { if ($setting_value !== $this->getPreference($setting_name)) { - Database::prepare( - "REPLACE INTO `##gedcom_setting` (gedcom_id, setting_name, setting_value)" . - " VALUES (:tree_id, :setting_name, LEFT(:setting_value, 255))" - )->execute([ - 'tree_id' => $this->id, - 'setting_name' => $setting_name, + DB::table('gedcom_setting')->updateOrInsert([ + 'gedcom_id' =>$this->id, + 'setting_name' => $setting_name, + ], [ 'setting_value' => $setting_value, ]); @@ -222,12 +224,11 @@ class Tree // There are lots of settings, and we need to fetch lots of them on every page // so it is quicker to fetch them all in one go. if (!array_key_exists($user->getUserId(), $this->user_preferences)) { - $this->user_preferences[$user->getUserId()] = Database::prepare( - "SELECT setting_name, setting_value FROM `##user_gedcom_setting` WHERE user_id = ? AND gedcom_id = ?" - )->execute([ - $user->getUserId(), - $this->id, - ])->fetchAssoc(); + $this->user_preferences[$user->getUserId()] = DB::table('user_gedcom_setting') + ->where('user_id', '=', $user->getUserId()) + ->where('gedcom_id', '=', $this->id) + ->pluck('setting_value', 'setting_name') + ->all(); } return $this->user_preferences[$user->getUserId()][$setting_name] ?? $default; @@ -246,25 +247,15 @@ class Tree { if ($this->getUserPreference($user, $setting_name) !== $setting_value) { // Update the database - if ($setting_value === '') { - Database::prepare( - "DELETE FROM `##user_gedcom_setting` WHERE gedcom_id = :tree_id AND user_id = :user_id AND setting_name = :setting_name" - )->execute([ - 'tree_id' => $this->id, - 'user_id' => $user->getUserId(), - 'setting_name' => $setting_name, - ]); - } else { - Database::prepare( - "REPLACE INTO `##user_gedcom_setting` (user_id, gedcom_id, setting_name, setting_value) VALUES (:user_id, :tree_id, :setting_name, LEFT(:setting_value, 255))" - )->execute([ - 'user_id' => $user->getUserId(), - 'tree_id' => $this->id, - 'setting_name' => $setting_name, - 'setting_value' => $setting_value, - ]); - } - // Update our cache + DB::table('user_gedcom_setting')->updateOrInsert([ + 'gedcom_id' => $this->id(), + 'user_id' =>$user->getUserId(), + 'setting_name' => $setting_name, + ], [ + 'setting_value' => $setting_value, + ]); + + // Update the cache $this->user_preferences[$user->getUserId()][$setting_name] = $setting_value; // Audit log of changes Log::addConfigurationLog('Tree preference "' . $setting_name . '" set to "' . $setting_value . '" for user "' . $user->getUserName() . '"', $this); @@ -293,6 +284,7 @@ class Tree public static function getAll(): array { if (empty(self::$trees)) { + /* $rows = Database::prepare( "SELECT g.gedcom_id AS tree_id, g.gedcom_name AS tree_name, gs1.setting_value AS tree_title" . " FROM `##gedcom` g" . @@ -314,9 +306,9 @@ class Tree Auth::id(), Auth::id(), ])->fetchAll(); + */ // Admins see all trees - /* $query = DB::table('gedcom') ->leftJoin('gedcom_setting', function (JoinClause $join): void { $join->on('gedcom_setting.gedcom_id', '=', 'gedcom.gedcom_id') @@ -331,7 +323,7 @@ class Tree ->orderBy('gedcom.sort_order') ->orderBy('gedcom_setting.setting_value'); - if (!Auth::isAdmin()) { + if (false && !Auth::isAdmin()) { $query ->join('gedcom_setting AS gs2','gs2.gedcom_id', '=', 'gedcom.gedcom_id') ->where('gs2.setting_name', '=', 'imported'); @@ -368,7 +360,6 @@ class Tree } $rows = $query->get(); - */ foreach ($rows as $row) { self::$trees[$row->tree_name] = new self((int) $row->tree_id, $row->tree_name, $row->tree_title); @@ -462,7 +453,7 @@ class Tree 'gedcom_name' => $tree_name, ]); - $tree_id = DB::connection()->getPdo()->lastInsertId(); + $tree_id = (int) DB::connection()->getPdo()->lastInsertId(); } catch (PDOException $ex) { // A tree with that name already exists? return self::findByName($tree_name); @@ -479,29 +470,35 @@ class Tree Module::setDefaultAccess($tree_id); // Set preferences from default tree - Database::prepare( - "INSERT INTO `##gedcom_setting` (gedcom_id, setting_name, setting_value)" . - " SELECT :tree_id, setting_name, setting_value" . - " FROM `##gedcom_setting` WHERE gedcom_id = -1" - )->execute([ - 'tree_id' => $tree_id, - ]); + (new Builder(DB::connection()))->from('gedcom_setting')->insertUsing( + ['gedcom_id', 'setting_name', 'setting_value'], + function (Builder $query) use ($tree_id): void { + $query + ->select([DB::raw($tree_id), 'setting_name', 'setting_value']) + ->from('gedcom_setting') + ->where('gedcom_id', '=', -1); + } + ); - Database::prepare( - "INSERT INTO `##default_resn` (gedcom_id, tag_type, resn)" . - " SELECT :tree_id, tag_type, resn" . - " FROM `##default_resn` WHERE gedcom_id = -1" - )->execute([ - 'tree_id' => $tree_id, - ]); + (new Builder(DB::connection()))->from('default_resn')->insertUsing( + ['gedcom_id', 'tag_type', 'resn'], + function (Builder $query) use ($tree_id): void { + $query + ->select([DB::raw($tree_id), 'tag_type', 'resn']) + ->from('default_resn') + ->where('gedcom_id', '=', -1); + } + ); - Database::prepare( - "INSERT INTO `##block` (gedcom_id, location, block_order, module_name)" . - " SELECT :tree_id, location, block_order, module_name" . - " FROM `##block` WHERE gedcom_id = -1" - )->execute([ - 'tree_id' => $tree_id, - ]); + (new Builder(DB::connection()))->from('block')->insertUsing( + ['gedcom_id', 'location', 'block_order', 'module_name'], + function (Builder $query) use ($tree_id): void { + $query + ->select([DB::raw($tree_id), 'location', 'block_order', 'module_name']) + ->from('block') + ->where('gedcom_id', '=', -1); + } + ); // Gedcom and privacy settings $tree->setPreference('CONTACT_USER_ID', (string) Auth::id()); @@ -534,9 +531,11 @@ class Tree /* I18N: This should be a common/default/placeholder name of an individual. Put slashes around the surname. */ $john_doe = I18N::translate('John /DOE/'); $note = I18N::translate('Edit this individual and replace their details with your own.'); - Database::prepare("INSERT INTO `##gedcom_chunk` (gedcom_id, chunk_data) VALUES (?, ?)")->execute([ - $tree_id, - "0 HEAD\n1 CHAR UTF-8\n0 @I1@ INDI\n1 NAME {$john_doe}\n1 SEX M\n1 BIRT\n2 DATE 01 JAN 1850\n2 NOTE {$note}\n0 TRLR\n", + $gedcom = "0 HEAD\n1 CHAR UTF-8\n0 @X1@ INDI\n1 NAME {$john_doe}\n1 SEX M\n1 BIRT\n2 DATE 01 JAN 1850\n2 NOTE {$note}\n0 TRLR\n"; + + DB::table('gedcom_chunk')->insert([ + 'gedcom_id' => $tree_id, + 'chunk_data' => $gedcom, ]); // Update our cache diff --git a/tests/TestCase.php b/tests/TestCase.php index ebdd34c3e0..12bc86e3c0 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -17,9 +17,9 @@ declare(strict_types=1); namespace Fisharebest\Webtrees; -use function basename; use Fisharebest\Webtrees\Schema\SeedDatabase; use Illuminate\Database\Capsule\Manager as DB; +use function basename; use function file_get_contents; /** @@ -27,20 +27,69 @@ use function file_get_contents; */ class TestCase extends \PHPUnit\Framework\TestCase { + protected static $uses_database = false; + + protected static $uses_transactions = false; + + /** + * Things to run once, before all the tests. + */ + public static function setUpBeforeClass() + { + parent::setUpBeforeClass(); + + defined('WT_BASE_URL') || define('WT_BASE_URL', 'http://localhost/'); + defined('WT_ROOT') || define('WT_ROOT', dirname(__DIR__) . '/'); + defined('WT_DATA_DIR') || define('WT_DATA_DIR', WT_ROOT . 'data/'); + + if (static::$uses_database) { + static::createTestDatabase(); + } + } + + /** + * Things to run once, AFTER all the tests. + */ + public static function tearDownAfterClass() + { + if (static::$uses_database) { + $pdo = DB::connection()->getPdo(); + $pdo = null; + } + + parent::tearDownAfterClass(); + } + + /** + * Things to run before every test. + */ protected function setUp() { parent::setUp(); defined('WT_BASE_URL') || define('WT_BASE_URL', 'http://localhost/'); - defined('WT_DATA_DIR') || define('WT_DATA_DIR', 'data/'); - defined('WT_ROOT') || define('WT_ROOT', ''); - I18N::init('en-US'); + defined('WT_ROOT') || define('WT_ROOT', dirname(__DIR__) . '/'); + defined('WT_DATA_DIR') || define('WT_DATA_DIR', WT_ROOT . 'data/'); + defined('WT_LOCALE') || define('WT_LOCALE', I18N::init('en-US')); + + if (static::$uses_database) { + DB::connection()->beginTransaction(); + } + } + + /** + * Things to run after every test + */ + protected function tearDown() { + if (static::$uses_database && static::$uses_transactions) { + DB::connection()->rollBack(); + } } /** * Create an SQLite in-memory database for testing */ - protected function createTestDatabase(): void + protected static function createTestDatabase(): void { $capsule = new DB(); $capsule->addConnection([ @@ -60,17 +109,18 @@ class TestCase extends \PHPUnit\Framework\TestCase * Import a GEDCOM file into the test database. * * @param string $gedcom_file + * + * @return Tree */ - protected function importTree(string $gedcom_file): void + protected function importTree(string $gedcom_file): Tree { - $x = DB::table('gedcom')->insert([ - 'gedcom_name' => basename($gedcom_file), - ]); - - var_dump($x);exit; + $tree = Tree::create(basename($gedcom_file), basename($gedcom_file)); DB::table('gedcom_chunk')->insert([ + 'gedcom_id' => $tree->id(), 'chunk_data' => file_get_contents($gedcom_file), ]); + + return $tree; } } diff --git a/tests/feature/MigrationTest.php b/tests/feature/MigrationTest.php deleted file mode 100644 index d6bdf44e4d..0000000000 --- a/tests/feature/MigrationTest.php +++ /dev/null @@ -1,40 +0,0 @@ -<?php -/** - * webtrees: online genealogy - * Copyright (C) 2018 webtrees development team - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ -declare(strict_types=1); - -namespace Fisharebest\Webtrees; - -/** - * Test the database migration process - */ -class MigrationTest extends \Fisharebest\Webtrees\TestCase -{ - protected function setUp() - { - parent::setUp(); - - $this->createTestDatabase(); - } - - /** - * Test that the class exists - * - * @return void - */ - public function testTablesExists() - { - } -} diff --git a/tests/feature/UserTest.php b/tests/feature/UserTest.php index 268b7d22bf..f216367c69 100644 --- a/tests/feature/UserTest.php +++ b/tests/feature/UserTest.php @@ -18,28 +18,144 @@ declare(strict_types=1); namespace Fisharebest\Webtrees; /** - * Test the database migration process + * Test the user functions + * + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled */ -class MigrationTest extends \Fisharebest\Webtrees\TestCase +class UserTest extends \Fisharebest\Webtrees\TestCase { - protected function setUp() + protected static $uses_database = true; + + protected static $uses_transactions = true; + + /** + * Test administrators. + * + * @covers \Fisharebest\Webtrees\Auth + * @covers \Fisharebest\Webtrees\Tree + * @covers \Fisharebest\Webtrees\User + * + * @return void + */ + public function testAdministrator(): void + { + // By default, new users are not admins. + $user = User::create('admin', 'Administrator', 'admin@example.com', 'secret'); + $this->assertFalse(Auth::isAdmin($user)); + + // Make the user a manager. + $user->setPreference('canadmin', '1'); + $this->assertTrue(Auth::isAdmin($user)); + + // Test that the current user is an admin. + $this->assertFalse(Auth::isAdmin()); + Auth::login($user); + $this->assertTrue(Auth::isAdmin()); + } + + /** + * Test managers. + * + * @covers \Fisharebest\Webtrees\Auth + * @covers \Fisharebest\Webtrees\Tree + * @covers \Fisharebest\Webtrees\User + * + * @return void + */ + public function testManager(): void + { + // By default, new users are not managers. + $user = User::create('manager', 'Manager', 'manager@example.com', 'secret'); + $tree = Tree::create('test', 'Test'); + $tree->setPreference('imported', '1'); + $this->assertFalse(Auth::isManager($tree, $user)); + + // Make the user a manager. + $tree->setUserPreference($user, 'canedit', 'admin'); + $this->assertTrue(Auth::isManager($tree, $user)); + + // Test that a specific user is a manager. + $this->assertFalse(Auth::isManager($tree)); + Auth::login($user); + $this->assertTrue(Auth::isManager($tree)); + } + + /** + * Test moderators. + * + * @covers \Fisharebest\Webtrees\Auth + * @covers \Fisharebest\Webtrees\Tree + * @covers \Fisharebest\Webtrees\User + * + * @return void + */ + public function testModerator(): void + { + // By default, new users are not moderators. + $user = User::create('moderator', 'Moderator', 'moderator@example.com', 'secret'); + $tree = Tree::create('test', 'Test'); + $this->assertFalse(Auth::isModerator($tree, $user)); + + // Make the user a moderator. + $tree->setUserPreference($user, 'canedit', 'accept'); + $this->assertTrue(Auth::isModerator($tree, $user)); + + // Test that a specific user is a moderator. + $this->assertFalse(Auth::isModerator($tree)); + Auth::login($user); + $this->assertTrue(Auth::isModerator($tree)); + } + + /** + * Test editors. + * + * @covers \Fisharebest\Webtrees\Auth + * @covers \Fisharebest\Webtrees\Tree + * @covers \Fisharebest\Webtrees\User + * + * @return void + */ + public function testEditor(): void { - parent::setUp(); + // By default, new users are not editors. + $user = User::create('editor', 'Editor', 'editor@example.com', 'secret'); + $tree = Tree::create('test', 'Test'); + $this->assertFalse(Auth::isEditor($tree, $user)); + + // Make the user an editor. + $tree->setUserPreference($user, 'canedit', 'edit'); + $this->assertTrue(Auth::isEditor($tree, $user)); - $this->createTestDatabase(); + // Test that a specific user is an editor. + $this->assertFalse(Auth::isEditor($tree)); + Auth::login($user); + $this->assertTrue(Auth::isEditor($tree)); } /** - * Test that the class exists + * Test members. + * + * @covers \Fisharebest\Webtrees\Auth + * @covers \Fisharebest\Webtrees\Tree + * @covers \Fisharebest\Webtrees\User * * @return void */ - public function testTablesExists() + public function testMember(): void { - $tree = $this->importTree(__DIR__ . '/../data/demo.ged'); + // By default, new users are not members. + $user = User::create('member', 'Member', 'member@example.com', 'secret'); + $tree = Tree::create('test', 'Test'); + $this->assertFalse(Auth::isMember($tree, $user)); + + // Make the user a members. + $tree->setUserPreference($user, 'canedit', 'edit'); + $this->assertTrue(Auth::isMember($tree, $user)); - $this->assertSame(1, $tree->id()); - $this->assertSame('demo', $tree->name()); - $this->assertSame('demo', $tree->title()); + // Test that a specific user is a member. + $this->assertFalse(Auth::isMember($tree)); + Auth::login($user); + $this->assertTrue(Auth::isMember($tree)); } } |
