Completed
Push — master ( bdc52b...f93e28 )
by Jonathan
36:53
created

SosaProvider::getRootIndiId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
/**
3
 * webtrees-lib: MyArtJaub library for webtrees
4
 *
5
 * @package MyArtJaub\Webtrees
6
 * @subpackage Sosa
7
 * @author Jonathan Jaubart <[email protected]>
8
 * @copyright Copyright (c) 2009-2016, Jonathan Jaubart
9
 * @license http://www.gnu.org/licenses/gpl.html GNU General Public License, version 3
10
 */
11
namespace MyArtJaub\Webtrees\Module\Sosa\Model;
12
13
use Fisharebest\Webtrees\Tree;
14
use Fisharebest\Webtrees\User;
15
use Fisharebest\Webtrees\Database;
16
use MyArtJaub\Webtrees\Functions\Functions;
17
use Fisharebest\Webtrees\Auth;
18
use Fisharebest\Webtrees\Individual;
19
20
/**
21
 * Provide Sosa data access
22
 */
23
class SosaProvider {
24
    
25
    /**
26
     * Maximum number of generation the database is able to hold.
27
     * @var int MAX_DB_GENERATIONS
28
     */
29
    const MAX_DB_GENERATIONS = 64;
30
    
31
    /**
32
     * System's default user (ID -1 in the database
33
     * @var User $default_user
34
     */
35
    protected static $default_user;
36
    
37
    /**
38
     * Reference user
39
     * @var User $user
40
     */
41
    protected $user;
42
    
43
    /**
44
     * Reference tree
45
     * @var Tree $tree
46
     */
47
    protected $tree;
48
    
49
    /**
50
     * Cached list of Sosa Individuals by generation
51
     * Format: key = generation, value = array ( sosa => Individual ID)
52
     * @var array $sosa_list_by_gen
53
     */
54
    protected $sosa_list_by_gen;
55
    
56
    /**
57
     * Cached list of Sosa Families by generation
58
     * Format: key = generation, value = array ( sosa => Family ID)
59
     * @var unknown $sosa_fam_list_by_gen
60
     */
61
    protected $sosa_fam_list_by_gen;
62
    
63
    /**
64
     * Cached array of statistics by generation
65
     * Format:  key = generation, 
66
     *          value = array(
67
     *              sosaCount, sosaTotalCount, diffSosaTotalCount, firstBirth, lastBirth, avgBirth
68
     *           )
69
     * @var array $statistics_tab
70
     */
71
    protected $statistics_tab;
72
    
73
    /**
74
     * Has the provider's initialisation completed
75
     * @var bool $is_setup
76
     */
77
    protected $is_setup;
78
    
79
    /**
80
     * Constructor for Sosa Provider.
81
     * A provider is defined in relation to a specific tree and reference user.
82
     * 
83
     * @param Tree $tree
84
     * @param User $user
85
     */
86
    public function __construct(Tree $tree, User $user = null) {
87
        if(self::$default_user === null) 
88
            self::$default_user = User::find(-1);
89
        
90
        $this->tree = $tree;
91
        $this->user = $user;
92
        $this->is_setup = true;
93
        if($this->user === null) $this->user = Auth::user();
94
        if(strlen($this->user->getUserId()) == 0) $this->user = self::$default_user;
95
        
96
        // Check if the user, or the default user, has a root already setup;
97
        if(empty($this->getRootIndiId())) {
98
            if($this->user == self::$default_user) {  // If the default user is not setup
99
                $this->is_setup = false;
100
            }
101
            else {
102
                $this->user = self::$default_user;
103
                $this->is_setup = $this->getRootIndiId() === null;
104
            }            
105
        }
106
    }
107
    
108
    /**
109
     * Returns is the Provider has been successfully set up
110
     * @return bool
111
     */
112
    public function isSetup() {
113
        return $this->is_setup;
114
    }
115
    
116
    /**
117
     * Return the root individual ID for the reference tree and user.
118
     * @return string Individual ID
119
     */
120
    public function getRootIndiId() {
121
        return $this->tree->getUserPreference($this->user, 'MAJ_SOSA_ROOT_ID');
122
    }
123
    
124
    /**
125
     * Return the root individual for the reference tree and user.
126
     * @return Individual Individual
127
     */
128
    public function getRootIndi() {
129
        $root_indi_id = $this->getRootIndiId();
130
        if(!empty($root_indi_id)) {
131
            return Individual::getInstance($root_indi_id, $this->tree);
132
        }
133
        return null;
134
    }
135
       
136
    /*****************
137
     * DATA CRUD LAYER
138
     *****************/
139
    
140
    /**
141
     * Remove all Sosa entries related to the gedcom file and user
142
     */
143
    public function deleteAll() {
144
        if(!$this->is_setup) return;
145
        Database::prepare(
146
            'DELETE FROM `##maj_sosa`'.
147
            ' WHERE majs_gedcom_id= :tree_id and majs_user_id = :user_id ')
148
            ->execute(array(
149
                'tree_id' => $this->tree->getTreeId(), 
150
                'user_id' => $this->user->getUserId()                
151
            ));
152
    }
153
    
154
    /**
155
     * Remove all ancestors of a sosa number
156
     * 
157
     * @param int $sosa
158
     */
159
    public function deleteAncestors($sosa) {
160
        if(!$this->is_setup) return;
161
        $gen = Functions::getGeneration($sosa);
162
        Database::prepare(
163
            'DELETE FROM `##maj_sosa`'.
164
            ' WHERE majs_gedcom_id=:tree_id and majs_user_id = :user_id' .
165
            ' AND majs_gen >= :gen' .
166
            ' AND FLOOR(majs_sosa / (POW(2, (majs_gen - :gen)))) = :sosa'
167
        )->execute(array(
168
            'tree_id' => $this->tree->getTreeId(), 
169
            'user_id' => $this->user->getUserId(),
170
            'gen' => $gen,
171
            'sosa' => $sosa
172
        ));
173
    }    
174
    
175
    /**
176
     * Insert (or update if already existing) a list of Sosa individuals
177
     * @param array $sosa_records
178
     */
179
    public function insertOrUpdate($sosa_records) {
180
        if(!$this->is_setup) return;
181
        
182
        $treeid = $this->tree->getTreeId();
183
        $userid = $this->user->getUserId();
184
        $questionmarks_table = array();
185
        $values_table = array();
186
        
187
        $i = 0;
188
        foreach  ($sosa_records as $row) {
189
            $gen = Functions::getGeneration($row['sosa']);
190
            if($gen <= self::MAX_DB_GENERATIONS) {
191
                $questionmarks_table[] = 
192
                    '(:tree_id'.$i.', :user_id'.$i.', :sosa'.$i.', :indi_id'.$i.', :gen'.$i.', :byear'.$i.', :dyear'.$i.')';
193
                $values_table = array_merge(
194
                    $values_table, 
195
                    array(
196
                        'tree_id'.$i => $treeid, 
197
                        'user_id'.$i => $userid, 
198
                        'sosa'.$i => $row['sosa'], 
199
                        'indi_id'.$i => $row['indi'], 
200
                        'gen'.$i => Functions::getGeneration($row['sosa']),
201
                        'byear'.$i => $row['birth_year'],
202
                        'dyear'.$i => $row['death_year']
203
                    )
204
                );
205
            }
206
            $i++;
207
        }
208
        
209
        $sql = 'REPLACE INTO `##maj_sosa`' .
210
            ' (majs_gedcom_id, majs_user_id, majs_sosa, majs_i_id, majs_gen, majs_birth_year, majs_death_year)' .
211
            ' VALUES '. implode(',', $questionmarks_table);
212
        Database::prepare($sql)->execute($values_table);
213
    }
214
    
215
    /****************
216
     * SIMPLE QUERIES
217
     ****************/
218
    
219
    /**
220
     * Returns the list of Sosa numbers to which an individual is related.
221
     * Format: key = sosa number, value = generation for the Sosa number
222
     * 
223
     * @param Individual $indi
224
     * @return array Array of sosa numbers
225
     */
226 View Code Duplication
    public function getSosaNumbers(Individual $indi) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
227
        if(!$this->is_setup) return array();
228
        return Database::prepare(
229
                'SELECT majs_sosa, majs_gen FROM `##maj_sosa`'.
230
                ' WHERE majs_i_id=:indi_id AND majs_gedcom_id=:tree_id AND majs_user_id=:user_id'
231
            )->execute(array(
232
                'indi_id' => $indi->getXref(), 
233
                'tree_id' => $this->tree->getTreeId(), 
234
                'user_id' => $this->user->getUserId()
235
            ))->fetchAssoc();
236
    }
237
    
238
    /**
239
     * Get the last generation of Sosa ancestors
240
     *
241
     * @return number Last generation if found, 1 otherwise
242
     */
243 View Code Duplication
    public function getLastGeneration() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
244
        if(!$this->is_setup) return;
245
        return Database::prepare(
246
                'SELECT MAX(majs_gen) FROM `##maj_sosa`'.
247
                ' WHERE majs_gedcom_id=:tree_id AND majs_user_id=:user_id'
248
            )->execute(array(
249
                'tree_id' => $this->tree->getTreeId(), 
250
                'user_id' => $this->user->getUserId()                
251
            ))->fetchOne() ?: 1;
252
    }
253
    
254
    /*************
255
     * SOSA LISTS
256
     *************/
257
    
258
    /**
259
     * Return the list of all sosas, with the generations it belongs to
260
     *
261
     * @param int $ged_id ID of the gedcom file
0 ignored issues
show
Bug introduced by
There is no parameter named $ged_id. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
262
     * @return array Associative array of Sosa ancestors, with their generation, comma separated
263
     */
264 View Code Duplication
    public function getAllSosaWithGenerations(){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
265
        if(!$this->is_setup) return array();
266
        return Database::prepare(
267
            'SELECT majs_i_id AS indi,' .
268
            ' GROUP_CONCAT(DISTINCT majs_gen ORDER BY majs_gen ASC SEPARATOR ",") AS generations' .
269
            ' FROM `##maj_sosa`' .
270
            ' WHERE majs_gedcom_id=:tree_id AND majs_user_id=:user_id' .
271
            ' GROUP BY majs_i_id'
272
        )->execute(array(
273
            'tree_id' => $this->tree->getTreeId(),
274
            'user_id' => $this->user->getUserId()
275
        ))->fetchAssoc();
276
    }
277
    
278
    /**
279
     * Get an associative array of Sosa individuals in generation G. Keys are Sosa numbers, values individuals.
280
     *
281
     * @param number $gen Generation
282
     * @return array Array of Sosa individuals
283
     */
284
    public function getSosaListAtGeneration($gen){
285
        if(!$this->is_setup) return array();
286
        if(!$this->sosa_list_by_gen)
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->sosa_list_by_gen of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
287
            $this->sosa_list_by_gen = array();
288
        
289
        if($gen){
290
            if(!isset($this->sosa_list_by_gen[$gen])){
291
                $this->sosa_list_by_gen[$gen] = Database::prepare(
292
                    'SELECT majs_sosa AS sosa, majs_i_id AS indi'.
293
                    ' FROM `##maj_sosa`'.
294
                    ' WHERE majs_gedcom_id=:tree_id AND majs_user_id=:user_id'.
295
                    ' AND majs_gen = :gen'.
296
                    ' ORDER BY majs_sosa ASC')
297
                ->execute(array(
298
                    'tree_id' => $this->tree->getTreeId(),
299
                    'user_id' => $this->user->getUserId(),
300
                    'gen' => $gen
301
                ))
302
                ->fetchAssoc();
303
            }
304
            return $this->sosa_list_by_gen[$gen];
305
        }
306
        return array();
307
    }
308
    
309
    /**
310
     * Get an associative array of Sosa families in generation G. Keys are Sosa numbers for the husband, values families.
311
     *
312
     * @param number $gen Generation
313
     * @return array Array of Sosa families
314
     */
315
    public function getFamilySosaListAtGeneration($gen){
316
        if(!$this->is_setup) return array();
317
        if(!$this->sosa_fam_list_by_gen)
318
            $this->sosa_fam_list_by_gen = array();
0 ignored issues
show
Documentation Bug introduced by
It seems like array() of type array is incompatible with the declared type object<MyArtJaub\Webtree...ule\Sosa\Model\unknown> of property $sosa_fam_list_by_gen.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
319
        
320
        if($gen){
321
            if(!isset($this->sosa_fam_list_by_gen[$gen])){
322
                $this->sosa_fam_list_by_gen[$gen] = Database::prepare(
323
                    'SELECT s1.majs_sosa AS sosa, f_id AS fam'.
324
                    ' FROM `##families`'.
325
                    ' INNER JOIN `##maj_sosa` AS s1 ON (`##families`.f_husb = s1.majs_i_id AND `##families`.f_file = s1.majs_gedcom_id)'.
326
                    ' INNER JOIN `##maj_sosa` AS s2 ON (`##families`.f_wife = s2.majs_i_id AND `##families`.f_file = s2.majs_gedcom_id)'.
327
                    ' WHERE s1.majs_sosa + 1 = s2.majs_sosa'.
328
                    ' AND s1.majs_gedcom_id= :tree_id AND s1.majs_user_id=:user_id'.
329
                    ' AND s2.majs_gedcom_id= :tree_id AND s2.majs_user_id=:user_id'.
330
                    ' AND s1.majs_gen = :gen'.
331
                    ' ORDER BY s1.majs_sosa ASC'
332
                    )
333
                    ->execute(array(
334
                        'tree_id' => $this->tree->getTreeId(),
335
                        'user_id' => $this->user->getUserId(),
336
                        'gen' => $gen
337
                    ))
338
                    ->fetchAssoc();
339
            }
340
            return $this->sosa_fam_list_by_gen[$gen];
341
        }
342
        return array();
343
    }
344
    
345
    /**
346
     * Get an associative array of Sosa individuals in generation G who are missing parents. Keys are Sosa numbers, values individuals.
347
     *
348
     * @param number $gen Generation
349
     * @return array Array of Sosa individuals
350
     */
351
    public function getMissingSosaListAtGeneration($gen){
352
        if(!$this->is_setup) return array();    
353
        if($gen){
354
            return $this->sosa_list_by_gen[$gen] = Database::prepare(
355
                'SELECT schild.majs_sosa sosa, schild.majs_i_id indi, sfat.majs_sosa IS NOT NULL has_father, smot.majs_sosa IS NOT NULL has_mother'.
356
                ' FROM `##maj_sosa` schild'.
357
                ' LEFT JOIN `##maj_sosa` sfat ON ((schild.majs_sosa * 2) = sfat.majs_sosa AND schild.majs_gedcom_id = sfat.majs_gedcom_id AND schild.majs_user_id = sfat.majs_user_id)'.
358
                ' LEFT JOIN `##maj_sosa` smot ON ((schild.majs_sosa * 2 + 1) = smot.majs_sosa AND schild.majs_gedcom_id = smot.majs_gedcom_id AND schild.majs_user_id = smot.majs_user_id)'.
359
                ' WHERE schild.majs_gedcom_id = :tree_id AND schild.majs_user_id = :user_id'.
360
                ' AND schild.majs_gen = :gen'.
361
                ' AND (sfat.majs_sosa IS NULL OR smot.majs_sosa IS NULL)'.
362
                ' ORDER BY schild.majs_sosa ASC')
363
                ->execute(array(
364
                    'tree_id' => $this->tree->getTreeId(),
365
                    'user_id' => $this->user->getUserId(),
366
                    'gen' => $gen - 1
367
                ))->fetchAll(\PDO::FETCH_ASSOC);
368
        }
369
        return array();
370
    }
371
    
372
    
373
    
374
    /*************
375
     * STATISTICS
376
     *************/
377
    /**
378
     * Get the statistic array detailed by generation.
379
     * Statistics for each generation are:
380
     * 	- The number of Sosa in generation
381
     * 	- The number of Sosa up to generation
382
     *  - The number of distinct Sosa up to generation
383
     *  - The year of the first birth in generation
384
     *  - The year of the last birth in generation
385
     *  - The average year of birth in generation
386
     *
387
     * @return array Statistics array
388
     */
389
    public function getStatisticsByGeneration() {
390
        if(!$this->is_setup) return array();
391
        if(!$this->statistics_tab) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->statistics_tab of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
392
            $this->statistics_tab = array();
393
            if($maxGeneration = $this->getLastGeneration()) {
394
                for ($gen = 1; $gen <= $maxGeneration; $gen++) {
395
                    $birthStats = $this->getStatsBirthYearInGeneration($gen);
396
                    $this->statistics_tab[$gen] = array(
397
                        'sosaCount'				=>	$this->getSosaCountAtGeneration($gen),
398
                        'sosaTotalCount'		=>	$this->getSosaCountUpToGeneration($gen),
399
                        'diffSosaTotalCount'	=>	$this->getDifferentSosaCountUpToGeneration($gen),
400
                        'firstBirth'			=>	$birthStats['first'],
401
                        'lastBirth'				=>	$birthStats['last'],
402
                        'avgBirth'				=>	$birthStats['avg']
403
                    );
404
                }
405
            }
406
        }
407
        return $this->statistics_tab;        
408
    }
409
    
410
	/**
411
	 * How many individuals exist in the tree.
412
	 *
413
	 * @return int
414
	 */
415
	public function getTotalIndividuals() {
416
	    if(!$this->is_setup) return 0;
417
	    return Database::prepare(
418
	        'SELECT SQL_CACHE COUNT(*) FROM `##individuals`' .
419
	        ' WHERE i_file = :tree_id')
420
	        ->execute(array('tree_id' => $this->tree->getTreeId()))
421
	        ->fetchOne() ?: 0;
422
	}
423
    
424
    /**
425
     * Get the total Sosa count for all generations
426
     *
427
     * @return number Number of Sosas
428
     */
429 View Code Duplication
    public function getSosaCount(){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
430
        if(!$this->is_setup) return 0;
431
        return Database::prepare(
432
            'SELECT SQL_CACHE COUNT(majs_sosa) FROM `##maj_sosa`' .
433
            ' WHERE majs_gedcom_id=:tree_id AND majs_user_id=:user_id')
434
            ->execute(array(
435
                'tree_id' => $this->tree->getTreeId(), 
436
                'user_id' => $this->user->getUserId() 
437
            ))->fetchOne() ?: 0;
438
    }
439
    
440
    /**
441
     * Get the number of Sosa in a specific generation.
442
     *
443
     * @param number $gen Generation
444
     * @return number Number of Sosas in generation
445
     */
446 View Code Duplication
    public function getSosaCountAtGeneration($gen){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
447
        if(!$this->is_setup) return 0;
448
        return Database::prepare(
449
            'SELECT SQL_CACHE COUNT(majs_sosa) FROM `##maj_sosa`' .
450
            ' WHERE majs_gedcom_id=:tree_id AND majs_user_id=:user_id'.
451
            ' AND majs_gen= :gen')
452
        ->execute(array(
453
                'tree_id' => $this->tree->getTreeId(), 
454
                'user_id' => $this->user->getUserId(),
455
                'gen' => $gen            
456
        ))->fetchOne() ?: 0;
457
    }
458
    
459
    /**
460
     * Get the total number of Sosa up to a specific generation.
461
     *
462
     * @param number $gen Generation
463
     * @return number Total number of Sosas up to generation
464
     */
465 View Code Duplication
    public function getSosaCountUpToGeneration($gen){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
466
        if(!$this->is_setup) return 0;
467
        return Database::prepare(
468
            'SELECT SQL_CACHE COUNT(majs_sosa) FROM `##maj_sosa`' .
469
            ' WHERE majs_gedcom_id=:tree_id AND majs_user_id=:user_id'.
470
            ' AND majs_gen <= :gen')
471
        ->execute(array(
472
                'tree_id' => $this->tree->getTreeId(), 
473
                'user_id' => $this->user->getUserId(),
474
                'gen' => $gen 
475
        ))->fetchOne() ?: 0;
476
    }
477
    
478
    /**
479
     * Get the total number of distinct Sosa individual for all generations.
480
     *
481
     * @return number Total number of distinct individual
482
     */
483 View Code Duplication
    public function getDifferentSosaCount(){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
484
        if(!$this->is_setup) return 0;
485
        return Database::prepare(
486
            'SELECT SQL_CACHE COUNT(DISTINCT majs_i_id) FROM `##maj_sosa`' .
487
            ' WHERE majs_gedcom_id=:tree_id AND majs_user_id=:user_id')
488
        ->execute(array(
489
                'tree_id' => $this->tree->getTreeId(), 
490
                'user_id' => $this->user->getUserId()
491
        ))->fetchOne() ?: 0;
492
    }
493
    
494
    /**
495
     * Get the number of distinct Sosa individual up to a specific generation.
496
     *
497
     * @param number $gen Generation
498
     * @return number Number of distinct Sosa individuals up to generation
499
     */
500 View Code Duplication
    public function getDifferentSosaCountUpToGeneration($gen){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
501
        if(!$this->is_setup) return 0;
502
        return Database::prepare(
503
            'SELECT SQL_CACHE COUNT(DISTINCT majs_i_id) FROM `##maj_sosa`' .
504
            ' WHERE majs_gedcom_id=:tree_id AND majs_user_id=:user_id'.
505
            ' AND majs_gen <= :gen')
506
        ->execute(array(
507
                'tree_id' => $this->tree->getTreeId(), 
508
                'user_id' => $this->user->getUserId(),
509
                'gen' => $gen 
510
        ))->fetchOne() ?: 0;
511
    }
512
    
513
    /**
514
     * Get an array of birth statistics for a specific generation
515
     * Statistics are :
516
     * 	- first : First birth year in generation
517
     *  - last : Last birth year in generation
518
     *  - avg : Average birth year
519
     *
520
     * @param number $gen Generation
521
     * @return array Birth statistics array
522
     */
523
    public function getStatsBirthYearInGeneration($gen){
524
        if(!$this->is_setup) return array('first' => 0, 'avg' => 0, 'last' => 0);
525
        return Database::prepare(
526
            'SELECT MIN(majs_birth_year) AS first, AVG(majs_birth_year) AS avg, MAX(majs_birth_year) AS last'.
527
            ' FROM `##maj_sosa`' .
528
            ' WHERE majs_gedcom_id=:tree_id AND majs_user_id=:user_id'.
529
            ' AND majs_gen=:gen AND NOT majs_birth_year = :birth_year')
530
            ->execute(array(
531
                'tree_id' => $this->tree->getTreeId(), 
532
                'user_id' => $this->user->getUserId(),
533
                'gen' => $gen,
534
                'birth_year' => 0))
535
            ->fetchOneRow(\PDO::FETCH_ASSOC) ?: array('first' => 0, 'avg' => 0, 'last' => 0);
536
    }
537
    
538
    /**
539
     * Get the mean generation time, based on a linear regression of birth years and generations
540
     *
541
     * @return number|NULL Mean generation time
542
     */
543
    public function getMeanGenerationTime(){
544
        if(!$this->is_setup) return;
545
        if(!$this->statistics_tab){
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->statistics_tab of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
546
            $this->getStatisticsByGeneration();
547
        }
548
        //Linear regression on x=generation and y=birthdate
549
        $sum_xy = 0;
550
        $sum_x=0;
551
        $sum_y=0;
552
        $sum_x2=0;
553
        $n=count($this->statistics_tab);
554
        foreach($this->statistics_tab as $gen=>$stats){
555
            $sum_xy+=$gen*$stats['avgBirth'];
556
            $sum_x+=$gen;
557
            $sum_y+=$stats['avgBirth'];
558
            $sum_x2+=$gen*$gen;
559
        }
560
        $denom=($n*$sum_x2)-($sum_x*$sum_x);
561
        if($denom!=0){
562
            return -(($n*$sum_xy)-($sum_x*$sum_y))/($denom);
563
        }
564
        return null;
565
    }
566
    
567
    /**
568
     * Return a computed array of statistics about the dispersion of ancestors across the ancestors
569
     * at a specified generation.
570
     * This statistics cannot be used for generations above 11, as it would cause a out of range in MySQL
571
     * 
572
     * Format: 
573
     *  - key : a base-2 representation of the ancestor at generation G for which exclusive ancestors have been found,
574
     *          -1 is used for shared ancestors
575
     *          For instance base2(0100) = base10(4) represent the maternal grand father
576
     *  - values: number of ancestors exclusively in the ancestors of the ancestor in key
577
     *  
578
     *  For instance a result at generation 3 could be :
579
     *      array (   -1        =>  12      -> 12 ancestors are shared by the grand-parents
580
     *                base10(1) =>  32      -> 32 ancestors are exclusive to the paternal grand-father
581
     *                base10(2) =>  25      -> 25 ancestors are exclusive to the paternal grand-mother
582
     *                base10(4) =>  12      -> 12 ancestors are exclusive to the maternal grand-father
583
     *                base10(8) =>  30      -> 30 ancestors are exclusive to the maternal grand-mother
584
     *            )
585
     *  
586
     * @param int $gen Reference generation
587
     * @return array
588
     */
589
    public function getAncestorDispersionForGen($gen) {
590
        if(!$this->is_setup || $gen > 11) return array();  // Going further than 11 gen will be out of range in the query
591
        return Database::prepare(
592
            'SELECT branches, count(i_id)'.
593
            ' FROM ('.
594
            '   SELECT i_id,'.
595
            '       CASE'.
596
            '           WHEN CEIL(LOG2(SUM(branch))) = LOG2(SUM(branch)) THEN SUM(branch)'.
597
            '           ELSE -1'.   // We put all ancestors shared between some branches in the same bucket
598
            '       END branches'.
599
            '   FROM ('.
600
            '       SELECT DISTINCT majs_i_id i_id,'.
601
            '           POW(2, FLOOR(majs_sosa / POW(2, (majs_gen - :gen))) - POW(2, :gen -1)) branch'.
602
            '       FROM `##maj_sosa`'.
603
            '       WHERE majs_gedcom_id = :tree_id AND majs_user_id = :user_id'.
604
            '           AND majs_gen >= :gen'.
605
            '   ) indistat'.
606
            '   GROUP BY i_id'.
607
            ') grouped'.
608
            ' GROUP BY branches')
609
            ->execute(array(
610
                'tree_id' => $this->tree->getTreeId(), 
611
                'user_id' => $this->user->getUserId(),
612
                'gen' => $gen
613
            ))->fetchAssoc() ?: array();
614
    }
615
    
616
               
617
}
618