Passed
Branch feature/2.0 (9789a8)
by Jonathan
14:17
created

SosaRecordsService   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 239
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 111
c 1
b 0
f 0
dl 0
loc 239
rs 10
wmc 15

9 Methods

Rating   Name   Duplication   Size   Complexity  
A deleteAll() 0 6 1
A maxSystemGenerations() 0 6 2
A listAncestorFamiliesAtGeneration() 0 23 1
A deleteAncestorsFrom() 0 11 1
A listAncestorsAtGeneration() 0 9 1
A generation() 0 3 1
A listMissingAncestorsAtGeneration() 0 32 2
A getSosaNumbers() 0 9 1
B insertOrUpdate() 0 52 5
1
<?php
2
3
/**
4
 * webtrees-lib: MyArtJaub library for webtrees
5
 *
6
 * @package MyArtJaub\Webtrees
7
 * @subpackage Sosa
8
 * @author Jonathan Jaubart <[email protected]>
9
 * @copyright Copyright (c) 2009-2020, Jonathan Jaubart
10
 * @license http://www.gnu.org/licenses/gpl.html GNU General Public License, version 3
11
 */
12
13
declare(strict_types=1);
14
15
namespace MyArtJaub\Webtrees\Module\Sosa\Services;
16
17
use Brick\Math\BigInteger;
18
use Fisharebest\Webtrees\Individual;
19
use Fisharebest\Webtrees\Tree;
20
use Fisharebest\Webtrees\User;
21
use Illuminate\Database\Capsule\Manager as DB;
22
use Illuminate\Database\Query\Builder;
23
use Illuminate\Database\Query\JoinClause;
24
use Illuminate\Support\Collection;
25
26
/**
27
 * Service for CRUD operations on Sosa records
28
 */
29
class SosaRecordsService
30
{
31
    /**
32
     * @var int $max_system_generations
33
     */
34
    private $max_system_generations;
35
36
    /**
37
     * Maximum number of generation the system is able to hold.
38
     * This is based on the size of the bigint SQL type (2^63) and the maximum PHP integer type
39
     *
40
     * @return int
41
     */
42
    public function maxSystemGenerations(): int
43
    {
44
        if ($this->max_system_generations === null) {
45
            $this->max_system_generations = min(63, $this->generation(PHP_INT_MAX));
46
        }
47
        return $this->max_system_generations;
48
    }
49
50
    /**
51
     * Calculate the generation of a sosa
52
     * Sosa 1 is of generation 1.
53
     *
54
     * @param int $sosa
55
     * @return int
56
     */
57
    public function generation(int $sosa): int
58
    {
59
        return BigInteger::of($sosa)->getBitLength();
60
    }
61
62
    /**
63
     * Returns all Sosa numbers associated to an Individual
64
     *
65
     * @param Tree $tree
66
     * @param User $user
67
     * @param Individual $indi
68
     * @return Collection
69
     */
70
    public function getSosaNumbers(Tree $tree, User $user, Individual $indi): Collection
71
    {
72
        return DB::table('maj_sosa')
73
            ->select(['majs_sosa', 'majs_gen'])
74
            ->where('majs_gedcom_id', '=', $tree->id())
75
            ->where('majs_user_id', '=', $user->id())
76
            ->where('majs_i_id', '=', $indi->xref())
77
            ->orderBy('majs_sosa')
78
            ->get()->pluck('majs_gen', 'majs_sosa');
79
    }
80
81
    /**
82
     * Return a list of the Sosa ancestors at a given generation
83
     *
84
     * @param Tree $tree
85
     * @param User $user
86
     * @param int $gen
87
     * @return Collection
88
     */
89
    public function listAncestorsAtGeneration(Tree $tree, User $user, int $gen): Collection
90
    {
91
        return DB::table('maj_sosa')
92
            ->select(['majs_sosa', 'majs_i_id'])
93
            ->where('majs_gedcom_id', '=', $tree->id())
94
            ->where('majs_user_id', '=', $user->id())
95
            ->where('majs_gen', '=', $gen)
96
            ->orderBy('majs_sosa')
97
            ->get();
98
    }
99
100
    /**
101
     * Return a list of the Sosa families at a given generation
102
     *
103
     * @param Tree $tree
104
     * @param User $user
105
     * @param int $gen
106
     * @return Collection
107
     */
108
    public function listAncestorFamiliesAtGeneration(Tree $tree, User $user, int $gen): Collection
109
    {
110
        $table_prefix = DB::connection()->getTablePrefix();
111
        return DB::table('families')
112
            ->join('maj_sosa AS sosa_husb', function (JoinClause $join) use ($tree, $user): void {
113
                // Link to family husband
114
                $join->on('families.f_file', '=', 'sosa_husb.majs_gedcom_id')
115
                    ->on('families.f_husb', '=', 'sosa_husb.majs_i_id')
116
                    ->where('sosa_husb.majs_gedcom_id', '=', $tree->id())
117
                    ->where('sosa_husb.majs_user_id', '=', $user->id());
118
            })
119
            ->join('maj_sosa AS sosa_wife', function (JoinClause $join) use ($tree, $user): void {
120
                // Link to family husband
121
                $join->on('families.f_file', '=', 'sosa_wife.majs_gedcom_id')
122
                ->on('families.f_wife', '=', 'sosa_wife.majs_i_id')
123
                ->where('sosa_wife.majs_gedcom_id', '=', $tree->id())
124
                ->where('sosa_wife.majs_user_id', '=', $user->id());
125
            })
126
            ->select(['sosa_husb.majs_sosa', 'families.f_id'])
127
            ->where('sosa_husb.majs_gen', '=', $gen)
128
            ->whereRaw($table_prefix . 'sosa_husb.majs_sosa + 1 = ' . $table_prefix . 'sosa_wife.majs_sosa')
129
            ->orderBy('sosa_husb.majs_sosa')
130
            ->get();
131
    }
132
133
    /**
134
     * Return a list of Sosa ancestors missing at a given generation.
135
     * It includes the reference of either parent if it is known.
136
     *
137
     * @param Tree $tree
138
     * @param User $user
139
     * @param int $gen
140
     * @return Collection
141
     */
142
    public function listMissingAncestorsAtGeneration(Tree $tree, User $user, int $gen): Collection
143
    {
144
        if ($gen == 1) {
145
            return collect();
146
        }
147
148
        $table_prefix = DB::connection()->getTablePrefix();
149
        return DB::table('maj_sosa AS sosa')
150
            ->select(['sosa.majs_i_id', 'sosa_fat.majs_i_id AS majs_fat_id', 'sosa_mot.majs_i_id AS majs_mot_id'])
151
            ->selectRaw('MIN(' . $table_prefix . 'sosa.majs_sosa) AS majs_sosa')
152
            ->leftJoin('maj_sosa AS sosa_fat', function (JoinClause $join) use ($tree, $user, $table_prefix): void {
153
                // Link to sosa's father
154
                $join->whereRaw($table_prefix . 'sosa_fat.majs_sosa = 2 * ' . $table_prefix . 'sosa.majs_sosa')
155
                    ->where('sosa_fat.majs_gedcom_id', '=', $tree->id())
156
                    ->where('sosa_fat.majs_user_id', '=', $user->id());
157
            })
158
            ->leftJoin('maj_sosa AS sosa_mot', function (JoinClause $join) use ($tree, $user, $table_prefix): void {
159
                // Link to sosa's mother
160
                $join->whereRaw($table_prefix . 'sosa_mot.majs_sosa = 2 * ' . $table_prefix . 'sosa.majs_sosa + 1')
161
                    ->where('sosa_mot.majs_gedcom_id', '=', $tree->id())
162
                    ->where('sosa_mot.majs_user_id', '=', $user->id());
163
            })
164
            ->where('sosa.majs_gedcom_id', '=', $tree->id())
165
            ->where('sosa.majs_user_id', '=', $user->id())
166
            ->where('sosa.majs_gen', '=', $gen - 1)
167
            ->where(function (Builder $query): void {
168
                $query->whereNull('sosa_fat.majs_i_id')
169
                    ->orWhereNull('sosa_mot.majs_i_id');
170
            })
171
            ->groupBy('sosa.majs_i_id', 'sosa_fat.majs_i_id', 'sosa_mot.majs_i_id')
172
            ->orderByRaw('MIN(' . $table_prefix . 'sosa.majs_sosa)')
173
            ->get();
174
    }
175
176
    /**
177
     * Remove all Sosa entries related to the gedcom file and user
178
     *
179
     * @param Tree $tree
180
     * @param User $user
181
     */
182
    public function deleteAll(Tree $tree, User $user): void
183
    {
184
        DB::table('maj_sosa')
185
            ->where('majs_gedcom_id', '=', $tree->id())
186
            ->where('majs_user_id', '=', $user->id())
187
            ->delete();
188
    }
189
190
    /**
191
     *
192
     * @param Tree $tree
193
     * @param User $user
194
     * @param int $sosa
195
     */
196
    public function deleteAncestorsFrom(Tree $tree, User $user, int $sosa): void
197
    {
198
        DB::table('maj_sosa')
199
            ->where('majs_gedcom_id', '=', $tree->id())
200
            ->where('majs_user_id', '=', $user->id())
201
            ->where('majs_sosa', '>=', $sosa)
202
            ->whereRaw(
203
                'FLOOR(majs_sosa / (POW(2, (majs_gen - ?)))) = ?',
204
                [$this->generation($sosa), $sosa]
205
            )
206
            ->delete();
207
    }
208
209
    /**
210
     * Insert (or update if already existing) a list of Sosa individuals
211
     *
212
     * @param Tree $tree
213
     * @param User $user
214
     * @param array $sosa_records
215
     */
216
    public function insertOrUpdate(Tree $tree, User $user, array $sosa_records): void
217
    {
218
        $mass_update = DB::connection()->getDriverName() === 'mysql';
219
220
        $bindings_placeholders = $bindings_values = [];
221
        foreach ($sosa_records as $i => $row) {
222
            $gen = $this->generation($row['sosa']);
223
            if ($gen <=  $this->maxSystemGenerations()) {
224
                if ($mass_update) {
225
                    $bindings_placeholders[] = '(:tree_id' . $i . ', :user_id' . $i . ', :sosa' . $i . ',' .
226
                        ' :indi_id' . $i . ', :gen' . $i . ',' .
227
                        ' :byear' . $i . ', :byearest' . $i . ', :dyear' . $i . ', :dyearest' . $i . ')';
228
                    $bindings_values = array_merge(
229
                        $bindings_values,
230
                        [
231
                            'tree_id' . $i => $tree->id(),
232
                            'user_id' . $i => $user->id(),
233
                            'sosa' . $i => $row['sosa'],
234
                            'indi_id' . $i => $row['indi'],
235
                            'gen' . $i => $gen,
236
                            'byear' . $i => $row['birth_year'],
237
                            'byearest' . $i => $row['birth_year_est'],
238
                            'dyear' . $i => $row['death_year'],
239
                            'dyearest' . $i => $row['death_year_est']
240
                        ]
241
                    );
242
                } else {
243
                    DB::table('maj_sosa')->updateOrInsert(
244
                        [ 'majs_gedcom_id' => $tree->id(), 'majs_user_id' => $user->id(), 'majs_sosa' => $row['sosa']],
245
                        [
246
                            'majs_i_id' => $row['indi'],
247
                            'majs_gen' => $gen,
248
                            'majs_birth_year' => $row['birth_year'],
249
                            'majs_birth_year_est' => $row['birth_year_est'],
250
                            'majs_death_year' => $row['death_year'],
251
                            'majs_death_year_est' => $row['death_year_est']
252
                        ]
253
                    );
254
                }
255
            }
256
        }
257
258
        if ($mass_update) {
259
            DB::connection()->statement(
260
                'INSERT INTO `' . DB::connection()->getTablePrefix() . 'maj_sosa`' .
261
                ' (majs_gedcom_id, majs_user_id, majs_sosa,' .
262
                '   majs_i_id, majs_gen, majs_birth_year, majs_birth_year_est, majs_death_year, majs_death_year_est)' .
263
                ' VALUES ' . implode(',', $bindings_placeholders) .
264
                ' ON DUPLICATE KEY UPDATE majs_i_id = VALUES(majs_i_id), majs_gen = VALUES(majs_gen),' .
265
                '   majs_birth_year = VALUES(majs_birth_year), majs_birth_year_est = VALUES(majs_birth_year_est),' .
266
                '   majs_death_year = VALUES(majs_death_year), majs_death_year_est = VALUES(majs_death_year_est)',
267
                $bindings_values
268
            );
269
        }
270
    }
271
}
272