Total Complexity | 993 |
Total Lines | 6903 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like Stats often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Stats, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
34 | class Stats { |
||
35 | /** @var Tree Generate statistics for a specified tree. */ |
||
36 | private $tree; |
||
37 | |||
38 | /** @var string[] All public functions are available as keywords - except these ones */ |
||
39 | private $public_but_not_allowed = [ |
||
40 | '__construct', 'embedTags', 'iso3166', 'getAllCountries', 'getAllTagsTable', 'getAllTagsText', 'statsPlaces', 'statsBirthQuery', 'statsDeathQuery', 'statsMarrQuery', 'statsAgeQuery', 'monthFirstChildQuery', 'statsChildrenQuery', 'statsMarrAgeQuery', |
||
41 | ]; |
||
42 | |||
43 | /** @var string[] List of GEDCOM media types */ |
||
44 | private $_media_types = ['audio', 'book', 'card', 'certificate', 'coat', 'document', 'electronic', 'magazine', 'manuscript', 'map', 'fiche', 'film', 'newspaper', 'painting', 'photo', 'tombstone', 'video', 'other']; |
||
45 | |||
46 | /** |
||
47 | * Create the statistics for a tree. |
||
48 | * |
||
49 | * @param Tree $tree Generate statistics for this tree |
||
50 | */ |
||
51 | public function __construct(Tree $tree) { |
||
52 | $this->tree = $tree; |
||
53 | } |
||
54 | |||
55 | /** |
||
56 | * Return a string of all supported tags and an example of its output in table row form. |
||
57 | * |
||
58 | * @return string |
||
59 | */ |
||
60 | public function getAllTagsTable() { |
||
61 | $examples = []; |
||
62 | foreach (get_class_methods($this) as $method) { |
||
63 | $reflection = new \ReflectionMethod($this, $method); |
||
64 | if ($reflection->isPublic() && !in_array($method, $this->public_but_not_allowed)) { |
||
65 | $examples[$method] = $this->$method(); |
||
66 | } |
||
67 | } |
||
68 | ksort($examples); |
||
69 | |||
70 | $html = ''; |
||
71 | foreach ($examples as $tag => $value) { |
||
72 | $html .= '<tr>'; |
||
73 | $html .= '<td class="list_value_wrap">' . $tag . '</td>'; |
||
74 | $html .= '<td class="list_value_wrap">' . $value . '</td>'; |
||
75 | $html .= '</tr>'; |
||
76 | } |
||
77 | |||
78 | return |
||
79 | '<table id="keywords" style="width:100%; table-layout:fixed"><thead>' . |
||
80 | '<tr>' . |
||
81 | '<th class="list_label_wrap width25">' . |
||
82 | I18N::translate('Embedded variable') . |
||
83 | '</th>' . |
||
84 | '<th class="list_label_wrap width75">' . |
||
85 | I18N::translate('Resulting value') . |
||
86 | '</th>' . |
||
87 | '</tr>' . |
||
88 | '</thead><tbody>' . |
||
89 | $html . |
||
90 | '</tbody></table>'; |
||
91 | } |
||
92 | |||
93 | /** |
||
94 | * Return a string of all supported tags in plain text. |
||
95 | * |
||
96 | * @return string |
||
97 | */ |
||
98 | public function getAllTagsText() { |
||
99 | $examples = []; |
||
100 | foreach (get_class_methods($this) as $method) { |
||
101 | $reflection = new \ReflectionMethod($this, $method); |
||
102 | if ($reflection->isPublic() && !in_array($method, $this->public_but_not_allowed)) { |
||
103 | $examples[$method] = $method; |
||
104 | } |
||
105 | } |
||
106 | ksort($examples); |
||
107 | |||
108 | return implode('<br>', $examples); |
||
109 | } |
||
110 | |||
111 | /** |
||
112 | * Get tags and their parsed results. |
||
113 | * |
||
114 | * @param string $text |
||
115 | * |
||
116 | * @return string[][] |
||
117 | */ |
||
118 | private function getTags($text) { |
||
119 | static $funcs; |
||
120 | |||
121 | // Retrive all class methods |
||
122 | isset($funcs) or $funcs = get_class_methods($this); |
||
|
|||
123 | |||
124 | // Extract all tags from the provided text |
||
125 | preg_match_all('/#([^#]+)(?=#)/', (string) $text, $match); |
||
126 | $tags = $match[1]; |
||
127 | $c = count($tags); |
||
128 | $new_tags = []; // tag to replace |
||
129 | $new_values = []; // value to replace it with |
||
130 | |||
131 | /* |
||
132 | * Parse block tags. |
||
133 | */ |
||
134 | for ($i = 0; $i < $c; $i++) { |
||
135 | $full_tag = $tags[$i]; |
||
136 | // Added for new parameter support |
||
137 | $params = explode(':', $tags[$i]); |
||
138 | if (count($params) > 1) { |
||
139 | $tags[$i] = array_shift($params); |
||
140 | } else { |
||
141 | $params = []; |
||
142 | } |
||
143 | |||
144 | // Generate the replacement value for the tag |
||
145 | if (method_exists($this, $tags[$i])) { |
||
146 | $new_tags[] = "#{$full_tag}#"; |
||
147 | $new_values[] = call_user_func_array([$this, $tags[$i]], [$params]); |
||
148 | } |
||
149 | } |
||
150 | |||
151 | return [$new_tags, $new_values]; |
||
152 | } |
||
153 | |||
154 | /** |
||
155 | * Embed tags in text |
||
156 | * |
||
157 | * @param string $text |
||
158 | * |
||
159 | * @return string |
||
160 | */ |
||
161 | public function embedTags($text) { |
||
162 | if (strpos($text, '#') !== false) { |
||
163 | list($new_tags, $new_values) = $this->getTags($text); |
||
164 | $text = str_replace($new_tags, $new_values, $text); |
||
165 | } |
||
166 | |||
167 | return $text; |
||
168 | } |
||
169 | |||
170 | /** |
||
171 | * Get the name used for GEDCOM files and URLs. |
||
172 | * |
||
173 | * @return string |
||
174 | */ |
||
175 | public function gedcomFilename() { |
||
176 | return $this->tree->getName(); |
||
177 | } |
||
178 | |||
179 | /** |
||
180 | * Get the internal ID number of the tree. |
||
181 | * |
||
182 | * @return int |
||
183 | */ |
||
184 | public function gedcomId() { |
||
185 | return $this->tree->getTreeId(); |
||
186 | } |
||
187 | |||
188 | /** |
||
189 | * Get the descriptive title of the tree. |
||
190 | * |
||
191 | * @return string |
||
192 | */ |
||
193 | public function gedcomTitle() { |
||
195 | } |
||
196 | |||
197 | /** |
||
198 | * Get information from the GEDCOM's HEAD record. |
||
199 | * |
||
200 | * @return string[] |
||
201 | */ |
||
202 | private function gedcomHead() { |
||
216 | } |
||
217 | |||
218 | /** |
||
219 | * Get the software originally used to create the GEDCOM file. |
||
220 | * |
||
221 | * @return string |
||
222 | */ |
||
223 | public function gedcomCreatedSoftware() { |
||
224 | $head = $this->gedcomHead(); |
||
225 | |||
226 | return $head[0]; |
||
227 | } |
||
228 | |||
229 | /** |
||
230 | * Get the version of software which created the GEDCOM file. |
||
231 | * |
||
232 | * @return string |
||
233 | */ |
||
234 | public function gedcomCreatedVersion() { |
||
235 | $head = $this->gedcomHead(); |
||
236 | // fix broken version string in Family Tree Maker |
||
237 | if (strstr($head[1], 'Family Tree Maker ')) { |
||
238 | $p = strpos($head[1], '(') + 1; |
||
239 | $p2 = strpos($head[1], ')'); |
||
240 | $head[1] = substr($head[1], $p, ($p2 - $p)); |
||
241 | } |
||
242 | // Fix EasyTree version |
||
243 | if ($head[2] == 'EasyTree') { |
||
244 | $head[1] = substr($head[1], 1); |
||
245 | } |
||
246 | |||
247 | return $head[1]; |
||
248 | } |
||
249 | |||
250 | /** |
||
251 | * Get the date the GEDCOM file was created. |
||
252 | * |
||
253 | * @return string |
||
254 | */ |
||
255 | public function gedcomDate() { |
||
256 | $head = GedcomRecord::getInstance('HEAD', $this->tree); |
||
257 | $fact = $head->getFirstFact('DATE'); |
||
258 | if ($fact) { |
||
259 | $date = new Date($fact->getValue()); |
||
260 | |||
261 | return $date->display(); |
||
262 | } |
||
263 | |||
264 | return ''; |
||
265 | } |
||
266 | |||
267 | /** |
||
268 | * When was this tree last updated? |
||
269 | * |
||
270 | * @return string |
||
271 | */ |
||
272 | public function gedcomUpdated() { |
||
273 | $row = Database::prepare( |
||
274 | "SELECT SQL_CACHE d_year, d_month, d_day FROM `##dates` WHERE d_julianday1 = (SELECT MAX(d_julianday1) FROM `##dates` WHERE d_file =? AND d_fact='CHAN') LIMIT 1" |
||
275 | )->execute([$this->tree->getTreeId()])->fetchOneRow(); |
||
276 | if ($row) { |
||
277 | $date = new Date("{$row->d_day} {$row->d_month} {$row->d_year}"); |
||
278 | |||
279 | return $date->display(); |
||
280 | } else { |
||
281 | return $this->gedcomDate(); |
||
282 | } |
||
283 | } |
||
284 | |||
285 | /** |
||
286 | * What is the significant individual from this tree? |
||
287 | * |
||
288 | * @return string |
||
289 | */ |
||
290 | public function gedcomRootId() { |
||
291 | return $this->tree->getPreference('PEDIGREE_ROOT_ID'); |
||
292 | } |
||
293 | |||
294 | /** |
||
295 | * Convert totals into percentages. |
||
296 | * |
||
297 | * @param string $total |
||
298 | * @param string $type |
||
299 | * |
||
300 | * @return string |
||
301 | */ |
||
302 | private function getPercentage($total, $type) { |
||
303 | switch ($type) { |
||
304 | case 'individual': |
||
305 | $type = $this->totalIndividualsQuery(); |
||
306 | break; |
||
307 | case 'family': |
||
308 | $type = $this->totalFamiliesQuery(); |
||
309 | break; |
||
310 | case 'source': |
||
311 | $type = $this->totalSourcesQuery(); |
||
312 | break; |
||
313 | case 'note': |
||
314 | $type = $this->totalNotesQuery(); |
||
315 | break; |
||
316 | case 'all': |
||
317 | default: |
||
318 | $type = $this->totalIndividualsQuery() + $this->totalFamiliesQuery() + $this->totalSourcesQuery(); |
||
319 | break; |
||
320 | } |
||
321 | if ($type == 0) { |
||
322 | return I18N::percentage(0, 1); |
||
323 | } else { |
||
324 | return I18N::percentage($total / $type, 1); |
||
325 | } |
||
326 | } |
||
327 | |||
328 | /** |
||
329 | * How many GEDCOM records exist in the tree. |
||
330 | * |
||
331 | * @return string |
||
332 | */ |
||
333 | public function totalRecords() { |
||
334 | return I18N::number($this->totalIndividualsQuery() + $this->totalFamiliesQuery() + $this->totalSourcesQuery()); |
||
335 | } |
||
336 | |||
337 | /** |
||
338 | * How many individuals exist in the tree. |
||
339 | * |
||
340 | * @return int |
||
341 | */ |
||
342 | private function totalIndividualsQuery() { |
||
343 | return (int) Database::prepare( |
||
344 | "SELECT SQL_CACHE COUNT(*) FROM `##individuals` WHERE i_file = :tree_id" |
||
345 | )->execute([ |
||
346 | 'tree_id' => $this->tree->getTreeId(), |
||
347 | ])->fetchOne(); |
||
348 | } |
||
349 | |||
350 | /** |
||
351 | * How many individuals exist in the tree. |
||
352 | * |
||
353 | * @return string |
||
354 | */ |
||
355 | public function totalIndividuals() { |
||
356 | return I18N::number($this->totalIndividualsQuery()); |
||
357 | } |
||
358 | |||
359 | /** |
||
360 | * How many individuals have one or more sources. |
||
361 | * |
||
362 | * @return int |
||
363 | */ |
||
364 | private function totalIndisWithSourcesQuery() { |
||
365 | return (int) Database::prepare( |
||
366 | "SELECT SQL_CACHE COUNT(DISTINCT i_id)" . |
||
367 | " FROM `##individuals` JOIN `##link` ON i_id = l_from AND i_file = l_file" . |
||
368 | " WHERE l_file = :tree_id AND l_type = 'SOUR'" |
||
369 | )->execute([ |
||
370 | 'tree_id' => $this->tree->getTreeId(), |
||
371 | ])->fetchOne(); |
||
372 | } |
||
373 | |||
374 | /** |
||
375 | * How many individuals have one or more sources. |
||
376 | * |
||
377 | * @return string |
||
378 | */ |
||
379 | public function totalIndisWithSources() { |
||
380 | return I18N::number($this->totalIndisWithSourcesQuery()); |
||
381 | } |
||
382 | |||
383 | /** |
||
384 | * Create a chart showing individuals with/without sources. |
||
385 | * |
||
386 | * @param string[] $params |
||
387 | * |
||
388 | * @return string |
||
389 | */ |
||
390 | public function chartIndisWithSources($params = []) { |
||
391 | $WT_STATS_CHART_COLOR1 = Theme::theme()->parameter('distribution-chart-no-values'); |
||
392 | $WT_STATS_CHART_COLOR2 = Theme::theme()->parameter('distribution-chart-high-values'); |
||
393 | $WT_STATS_S_CHART_X = Theme::theme()->parameter('stats-small-chart-x'); |
||
394 | $WT_STATS_S_CHART_Y = Theme::theme()->parameter('stats-small-chart-y'); |
||
395 | |||
396 | if (isset($params[0]) && $params[0] != '') { |
||
397 | $size = strtolower($params[0]); |
||
398 | } else { |
||
399 | $size = $WT_STATS_S_CHART_X . 'x' . $WT_STATS_S_CHART_Y; |
||
400 | } |
||
401 | if (isset($params[1]) && $params[1] != '') { |
||
402 | $color_from = strtolower($params[1]); |
||
403 | } else { |
||
404 | $color_from = $WT_STATS_CHART_COLOR1; |
||
405 | } |
||
406 | if (isset($params[2]) && $params[2] != '') { |
||
407 | $color_to = strtolower($params[2]); |
||
408 | } else { |
||
409 | $color_to = $WT_STATS_CHART_COLOR2; |
||
410 | } |
||
411 | $sizes = explode('x', $size); |
||
412 | $tot_indi = $this->totalIndividualsQuery(); |
||
413 | if ($tot_indi == 0) { |
||
414 | return ''; |
||
415 | } else { |
||
416 | $tot_sindi_per = round($this->totalIndisWithSourcesQuery() / $tot_indi, 3); |
||
417 | $chd = $this->arrayToExtendedEncoding([100 - 100 * $tot_sindi_per, 100 * $tot_sindi_per]); |
||
418 | $chl = I18N::translate('Without sources') . ' - ' . I18N::percentage(1 - $tot_sindi_per, 1) . '|' . |
||
419 | I18N::translate('With sources') . ' - ' . I18N::percentage($tot_sindi_per, 1); |
||
420 | $chart_title = I18N::translate('Individuals with sources'); |
||
421 | |||
422 | return '<img src="https://chart.googleapis.com/chart?cht=p3&chd=e:' . $chd . '&chs=' . $size . '&chco=' . $color_from . ',' . $color_to . '&chf=bg,s,ffffff00&chl=' . rawurlencode($chl) . '" width="' . $sizes[0] . '" height="' . $sizes[1] . '" alt="' . $chart_title . '" title="' . $chart_title . '">'; |
||
423 | } |
||
424 | } |
||
425 | |||
426 | /** |
||
427 | * Show the total individuals as a percentage. |
||
428 | * |
||
429 | * @return string |
||
430 | */ |
||
431 | public function totalIndividualsPercentage() { |
||
432 | return $this->getPercentage($this->totalIndividualsQuery(), 'all'); |
||
433 | } |
||
434 | |||
435 | /** |
||
436 | * Count the total families. |
||
437 | * |
||
438 | * @return int |
||
439 | */ |
||
440 | private function totalFamiliesQuery() { |
||
441 | return (int) Database::prepare( |
||
442 | "SELECT SQL_CACHE COUNT(*) FROM `##families` WHERE f_file = :tree_id" |
||
443 | )->execute([ |
||
444 | 'tree_id' => $this->tree->getTreeId(), |
||
445 | ])->fetchOne(); |
||
446 | } |
||
447 | |||
448 | /** |
||
449 | * Count the total families. |
||
450 | * |
||
451 | * @return string |
||
452 | */ |
||
453 | public function totalFamilies() { |
||
454 | return I18N::number($this->totalFamiliesQuery()); |
||
455 | } |
||
456 | |||
457 | /** |
||
458 | * Count the families with source records. |
||
459 | * |
||
460 | * @return int |
||
461 | */ |
||
462 | private function totalFamsWithSourcesQuery() { |
||
463 | return (int) Database::prepare( |
||
464 | "SELECT SQL_CACHE COUNT(DISTINCT f_id)" . |
||
465 | " FROM `##families` JOIN `##link` ON f_id = l_from AND f_file = l_file" . |
||
466 | " WHERE l_file = :tree_id AND l_type = 'SOUR'" |
||
467 | )->execute([ |
||
468 | 'tree_id' => $this->tree->getTreeId(), |
||
469 | ])->fetchOne(); |
||
470 | } |
||
471 | |||
472 | /** |
||
473 | * Count the families with with source records. |
||
474 | * |
||
475 | * @return string |
||
476 | */ |
||
477 | public function totalFamsWithSources() { |
||
478 | return I18N::number($this->totalFamsWithSourcesQuery()); |
||
479 | } |
||
480 | |||
481 | /** |
||
482 | * Create a chart of individuals with/without sources. |
||
483 | * |
||
484 | * @param string[] $params |
||
485 | * |
||
486 | * @return string |
||
487 | */ |
||
488 | public function chartFamsWithSources($params = []) { |
||
489 | $WT_STATS_CHART_COLOR1 = Theme::theme()->parameter('distribution-chart-no-values'); |
||
490 | $WT_STATS_CHART_COLOR2 = Theme::theme()->parameter('distribution-chart-high-values'); |
||
491 | $WT_STATS_S_CHART_X = Theme::theme()->parameter('stats-small-chart-x'); |
||
492 | $WT_STATS_S_CHART_Y = Theme::theme()->parameter('stats-small-chart-y'); |
||
493 | |||
494 | if (isset($params[0]) && $params[0] != '') { |
||
495 | $size = strtolower($params[0]); |
||
496 | } else { |
||
497 | $size = $WT_STATS_S_CHART_X . 'x' . $WT_STATS_S_CHART_Y; |
||
498 | } |
||
499 | if (isset($params[1]) && $params[1] != '') { |
||
500 | $color_from = strtolower($params[1]); |
||
501 | } else { |
||
502 | $color_from = $WT_STATS_CHART_COLOR1; |
||
503 | } |
||
504 | if (isset($params[2]) && $params[2] != '') { |
||
505 | $color_to = strtolower($params[2]); |
||
506 | } else { |
||
507 | $color_to = $WT_STATS_CHART_COLOR2; |
||
508 | } |
||
509 | $sizes = explode('x', $size); |
||
510 | $tot_fam = $this->totalFamiliesQuery(); |
||
511 | if ($tot_fam == 0) { |
||
512 | return ''; |
||
513 | } else { |
||
514 | $tot_sfam_per = round($this->totalFamsWithSourcesQuery() / $tot_fam, 3); |
||
515 | $chd = $this->arrayToExtendedEncoding([100 - 100 * $tot_sfam_per, 100 * $tot_sfam_per]); |
||
516 | $chl = I18N::translate('Without sources') . ' - ' . I18N::percentage(1 - $tot_sfam_per, 1) . '|' . |
||
517 | I18N::translate('With sources') . ' - ' . I18N::percentage($tot_sfam_per, 1); |
||
518 | $chart_title = I18N::translate('Families with sources'); |
||
519 | |||
520 | return "<img src=\"https://chart.googleapis.com/chart?cht=p3&chd=e:{$chd}&chs={$size}&chco={$color_from},{$color_to}&chf=bg,s,ffffff00&chl={$chl}\" width=\"{$sizes[0]}\" height=\"{$sizes[1]}\" alt=\"" . $chart_title . '" title="' . $chart_title . '" />'; |
||
521 | } |
||
522 | } |
||
523 | |||
524 | /** |
||
525 | * Show the total families as a percentage. |
||
526 | * |
||
527 | * @return string |
||
528 | */ |
||
529 | public function totalFamiliesPercentage() { |
||
530 | return $this->getPercentage($this->totalFamiliesQuery(), 'all'); |
||
531 | } |
||
532 | |||
533 | /** |
||
534 | * Count the total number of sources. |
||
535 | * |
||
536 | * @return int |
||
537 | */ |
||
538 | private function totalSourcesQuery() { |
||
539 | return (int) Database::prepare( |
||
540 | "SELECT SQL_CACHE COUNT(*) FROM `##sources` WHERE s_file = :tree_id" |
||
541 | )->execute([ |
||
542 | 'tree_id' => $this->tree->getTreeId(), |
||
543 | ])->fetchOne(); |
||
544 | } |
||
545 | |||
546 | /** |
||
547 | * Count the total number of sources. |
||
548 | * |
||
549 | * @return string |
||
550 | */ |
||
551 | public function totalSources() { |
||
552 | return I18N::number($this->totalSourcesQuery()); |
||
553 | } |
||
554 | |||
555 | /** |
||
556 | * Show the number of sources as a percentage. |
||
557 | * |
||
558 | * @return string |
||
559 | */ |
||
560 | public function totalSourcesPercentage() { |
||
561 | return $this->getPercentage($this->totalSourcesQuery(), 'all'); |
||
562 | } |
||
563 | |||
564 | /** |
||
565 | * Count the number of notes. |
||
566 | * |
||
567 | * @return int |
||
568 | */ |
||
569 | private function totalNotesQuery() { |
||
570 | return (int) Database::prepare( |
||
571 | "SELECT SQL_CACHE COUNT(*) FROM `##other` WHERE o_type='NOTE' AND o_file = :tree_id" |
||
572 | )->execute([ |
||
573 | 'tree_id' => $this->tree->getTreeId(), |
||
574 | ])->fetchOne(); |
||
575 | } |
||
576 | |||
577 | /** |
||
578 | * Count the number of notes. |
||
579 | * |
||
580 | * @return string |
||
581 | */ |
||
582 | public function totalNotes() { |
||
583 | return I18N::number($this->totalNotesQuery()); |
||
584 | } |
||
585 | |||
586 | /** |
||
587 | * Show the number of notes as a percentage. |
||
588 | * |
||
589 | * @return string |
||
590 | */ |
||
591 | public function totalNotesPercentage() { |
||
592 | return $this->getPercentage($this->totalNotesQuery(), 'all'); |
||
593 | } |
||
594 | |||
595 | /** |
||
596 | * Count the number of repositories. |
||
597 | * |
||
598 | * @return int |
||
599 | */ |
||
600 | private function totalRepositoriesQuery() { |
||
601 | return (int) Database::prepare( |
||
602 | "SELECT SQL_CACHE COUNT(*) FROM `##other` WHERE o_type='REPO' AND o_file = :tree_id" |
||
603 | )->execute([ |
||
604 | 'tree_id' => $this->tree->getTreeId(), |
||
605 | ])->fetchOne(); |
||
606 | } |
||
607 | |||
608 | /** |
||
609 | * Count the number of repositories |
||
610 | * |
||
611 | * @return string |
||
612 | */ |
||
613 | public function totalRepositories() { |
||
614 | return I18N::number($this->totalRepositoriesQuery()); |
||
615 | } |
||
616 | |||
617 | /** |
||
618 | * Show the total number of repositories as a percentage. |
||
619 | * |
||
620 | * @return string |
||
621 | */ |
||
622 | public function totalRepositoriesPercentage() { |
||
623 | return $this->getPercentage($this->totalRepositoriesQuery(), 'all'); |
||
624 | } |
||
625 | |||
626 | /** |
||
627 | * Count the surnames. |
||
628 | * |
||
629 | * @param string[] $params |
||
630 | * |
||
631 | * @return string |
||
632 | */ |
||
633 | public function totalSurnames($params = []) { |
||
634 | if ($params) { |
||
635 | $opt = 'IN (' . implode(',', array_fill(0, count($params), '?')) . ')'; |
||
636 | $distinct = ''; |
||
637 | } else { |
||
638 | $opt = "IS NOT NULL"; |
||
639 | $distinct = 'DISTINCT'; |
||
640 | } |
||
641 | $params[] = $this->tree->getTreeId(); |
||
642 | $total = |
||
643 | Database::prepare( |
||
644 | "SELECT SQL_CACHE COUNT({$distinct} n_surn COLLATE '" . I18N::collation() . "')" . |
||
645 | " FROM `##name`" . |
||
646 | " WHERE n_surn COLLATE '" . I18N::collation() . "' {$opt} AND n_file=?" |
||
647 | )->execute( |
||
648 | $params |
||
649 | )->fetchOne(); |
||
650 | |||
651 | return I18N::number($total); |
||
652 | } |
||
653 | |||
654 | /** |
||
655 | * Count the number of distinct given names, or count the number of |
||
656 | * occurrences of a specific name or names. |
||
657 | * |
||
658 | * @param string[] $params |
||
659 | * |
||
660 | * @return string |
||
661 | */ |
||
662 | public function totalGivennames($params = []) { |
||
663 | if ($params) { |
||
664 | $qs = implode(',', array_fill(0, count($params), '?')); |
||
665 | $params[] = $this->tree->getTreeId(); |
||
666 | $total = |
||
667 | Database::prepare("SELECT SQL_CACHE COUNT( n_givn) FROM `##name` WHERE n_givn IN ({$qs}) AND n_file=?") |
||
668 | ->execute($params) |
||
669 | ->fetchOne(); |
||
670 | } else { |
||
671 | $total = |
||
672 | Database::prepare("SELECT SQL_CACHE COUNT(DISTINCT n_givn) FROM `##name` WHERE n_givn IS NOT NULL AND n_file=?") |
||
673 | ->execute([$this->tree->getTreeId()]) |
||
674 | ->fetchOne(); |
||
675 | } |
||
676 | |||
677 | return I18N::number($total); |
||
678 | } |
||
679 | |||
680 | /** |
||
681 | * Count the number of events (with dates). |
||
682 | * |
||
683 | * @param string[] $params |
||
684 | * |
||
685 | * @return string |
||
686 | */ |
||
687 | public function totalEvents($params = []) { |
||
688 | $sql = "SELECT SQL_CACHE COUNT(*) AS tot FROM `##dates` WHERE d_file=?"; |
||
689 | $vars = [$this->tree->getTreeId()]; |
||
690 | |||
691 | $no_types = ['HEAD', 'CHAN']; |
||
692 | if ($params) { |
||
693 | $types = []; |
||
694 | foreach ($params as $type) { |
||
695 | if (substr($type, 0, 1) == '!') { |
||
696 | $no_types[] = substr($type, 1); |
||
697 | } else { |
||
698 | $types[] = $type; |
||
699 | } |
||
700 | } |
||
701 | if ($types) { |
||
702 | $sql .= ' AND d_fact IN (' . implode(', ', array_fill(0, count($types), '?')) . ')'; |
||
703 | $vars = array_merge($vars, $types); |
||
704 | } |
||
705 | } |
||
706 | $sql .= ' AND d_fact NOT IN (' . implode(', ', array_fill(0, count($no_types), '?')) . ')'; |
||
707 | $vars = array_merge($vars, $no_types); |
||
708 | |||
709 | return I18N::number(Database::prepare($sql)->execute($vars)->fetchOne()); |
||
710 | } |
||
711 | |||
712 | /** |
||
713 | * Count the number of births. |
||
714 | * |
||
715 | * @return string |
||
716 | */ |
||
717 | public function totalEventsBirth() { |
||
718 | return $this->totalEvents(explode('|', WT_EVENTS_BIRT)); |
||
719 | } |
||
720 | |||
721 | /** |
||
722 | * Count the number of births. |
||
723 | * |
||
724 | * @return string |
||
725 | */ |
||
726 | public function totalBirths() { |
||
727 | return $this->totalEvents(['BIRT']); |
||
728 | } |
||
729 | |||
730 | /** |
||
731 | * Count the number of deaths. |
||
732 | * |
||
733 | * @return string |
||
734 | */ |
||
735 | public function totalEventsDeath() { |
||
736 | return $this->totalEvents(explode('|', WT_EVENTS_DEAT)); |
||
737 | } |
||
738 | |||
739 | /** |
||
740 | * Count the number of deaths. |
||
741 | * |
||
742 | * @return string |
||
743 | */ |
||
744 | public function totalDeaths() { |
||
745 | return $this->totalEvents(['DEAT']); |
||
746 | } |
||
747 | |||
748 | /** |
||
749 | * Count the number of marriages. |
||
750 | * |
||
751 | * @return string |
||
752 | */ |
||
753 | public function totalEventsMarriage() { |
||
754 | return $this->totalEvents(explode('|', WT_EVENTS_MARR)); |
||
755 | } |
||
756 | |||
757 | /** |
||
758 | * Count the number of marriages. |
||
759 | * |
||
760 | * @return string |
||
761 | */ |
||
762 | public function totalMarriages() { |
||
763 | return $this->totalEvents(['MARR']); |
||
764 | } |
||
765 | |||
766 | /** |
||
767 | * Count the number of divorces. |
||
768 | * |
||
769 | * @return string |
||
770 | */ |
||
771 | public function totalEventsDivorce() { |
||
772 | return $this->totalEvents(explode('|', WT_EVENTS_DIV)); |
||
773 | } |
||
774 | |||
775 | /** |
||
776 | * Count the number of divorces. |
||
777 | * |
||
778 | * @return string |
||
779 | */ |
||
780 | public function totalDivorces() { |
||
781 | return $this->totalEvents(['DIV']); |
||
782 | } |
||
783 | |||
784 | /** |
||
785 | * Count the number of other events. |
||
786 | * |
||
787 | * @return string |
||
788 | */ |
||
789 | public function totalEventsOther() { |
||
790 | $facts = array_merge(explode('|', WT_EVENTS_BIRT . '|' . WT_EVENTS_MARR . '|' . WT_EVENTS_DIV . '|' . WT_EVENTS_DEAT)); |
||
791 | $no_facts = []; |
||
792 | foreach ($facts as $fact) { |
||
793 | $fact = '!' . str_replace('\'', '', $fact); |
||
794 | $no_facts[] = $fact; |
||
795 | } |
||
796 | |||
797 | return $this->totalEvents($no_facts); |
||
798 | } |
||
799 | |||
800 | /** |
||
801 | * Count the number of males. |
||
802 | * |
||
803 | * @return int |
||
804 | */ |
||
805 | private function totalSexMalesQuery() { |
||
806 | return (int) Database::prepare( |
||
807 | "SELECT SQL_CACHE COUNT(*) FROM `##individuals` WHERE i_file = :tree_id AND i_sex = 'M'" |
||
808 | )->execute([ |
||
809 | 'tree_id' => $this->tree->getTreeId(), |
||
810 | ])->fetchOne(); |
||
811 | } |
||
812 | |||
813 | /** |
||
814 | * Count the number of males. |
||
815 | * |
||
816 | * @return string |
||
817 | */ |
||
818 | public function totalSexMales() { |
||
819 | return I18N::number($this->totalSexMalesQuery()); |
||
820 | } |
||
821 | |||
822 | /** |
||
823 | * Count the number of males |
||
824 | * |
||
825 | * @return string |
||
826 | */ |
||
827 | public function totalSexMalesPercentage() { |
||
828 | return $this->getPercentage($this->totalSexMalesQuery(), 'individual'); |
||
829 | } |
||
830 | |||
831 | /** |
||
832 | * Count the number of females. |
||
833 | * |
||
834 | * @return int |
||
835 | */ |
||
836 | private function totalSexFemalesQuery() { |
||
837 | return (int) Database::prepare( |
||
838 | "SELECT SQL_CACHE COUNT(*) FROM `##individuals` WHERE i_file = :tree_id AND i_sex = 'F'" |
||
839 | )->execute([ |
||
840 | 'tree_id' => $this->tree->getTreeId(), |
||
841 | ])->fetchOne(); |
||
842 | } |
||
843 | |||
844 | /** |
||
845 | * Count the number of females. |
||
846 | * |
||
847 | * @return string |
||
848 | */ |
||
849 | public function totalSexFemales() { |
||
850 | return I18N::number($this->totalSexFemalesQuery()); |
||
851 | } |
||
852 | |||
853 | /** |
||
854 | * Count the number of females. |
||
855 | * |
||
856 | * @return string |
||
857 | */ |
||
858 | public function totalSexFemalesPercentage() { |
||
859 | return $this->getPercentage($this->totalSexFemalesQuery(), 'individual'); |
||
860 | } |
||
861 | |||
862 | /** |
||
863 | * Count the number of individuals with unknown sex. |
||
864 | * |
||
865 | * @return int |
||
866 | */ |
||
867 | private function totalSexUnknownQuery() { |
||
868 | return (int) Database::prepare( |
||
869 | "SELECT SQL_CACHE COUNT(*) FROM `##individuals` WHERE i_file = :tree_id AND i_sex = 'U'" |
||
870 | )->execute([ |
||
871 | 'tree_id' => $this->tree->getTreeId(), |
||
872 | ])->fetchOne(); |
||
873 | } |
||
874 | |||
875 | /** |
||
876 | * Count the number of individuals with unknown sex. |
||
877 | * |
||
878 | * @return string |
||
879 | */ |
||
880 | public function totalSexUnknown() { |
||
881 | return I18N::number($this->totalSexUnknownQuery()); |
||
882 | } |
||
883 | |||
884 | /** |
||
885 | * Count the number of individuals with unknown sex. |
||
886 | * |
||
887 | * @return string |
||
888 | */ |
||
889 | public function totalSexUnknownPercentage() { |
||
890 | return $this->getPercentage($this->totalSexUnknownQuery(), 'individual'); |
||
891 | } |
||
892 | |||
893 | /** |
||
894 | * Generate a chart showing sex distribution. |
||
895 | * |
||
896 | * @param string[] $params |
||
897 | * |
||
898 | * @return string |
||
899 | */ |
||
900 | public function chartSex($params = []) { |
||
901 | $WT_STATS_S_CHART_X = Theme::theme()->parameter('stats-small-chart-x'); |
||
902 | $WT_STATS_S_CHART_Y = Theme::theme()->parameter('stats-small-chart-y'); |
||
903 | |||
904 | if (isset($params[0]) && $params[0] != '') { |
||
905 | $size = strtolower($params[0]); |
||
906 | } else { |
||
907 | $size = $WT_STATS_S_CHART_X . "x" . $WT_STATS_S_CHART_Y; |
||
908 | } |
||
909 | if (isset($params[1]) && $params[1] != '') { |
||
910 | $color_female = strtolower($params[1]); |
||
911 | } else { |
||
912 | $color_female = 'ffd1dc'; |
||
913 | } |
||
914 | if (isset($params[2]) && $params[2] != '') { |
||
915 | $color_male = strtolower($params[2]); |
||
916 | } else { |
||
917 | $color_male = '84beff'; |
||
918 | } |
||
919 | if (isset($params[3]) && $params[3] != '') { |
||
920 | $color_unknown = strtolower($params[3]); |
||
921 | } else { |
||
922 | $color_unknown = '777777'; |
||
923 | } |
||
924 | $sizes = explode('x', $size); |
||
925 | // Raw data - for calculation |
||
926 | $tot_f = $this->totalSexFemalesQuery(); |
||
927 | $tot_m = $this->totalSexMalesQuery(); |
||
928 | $tot_u = $this->totalSexUnknownQuery(); |
||
929 | $tot = $tot_f + $tot_m + $tot_u; |
||
930 | // I18N data - for display |
||
931 | $per_f = $this->totalSexFemalesPercentage(); |
||
932 | $per_m = $this->totalSexMalesPercentage(); |
||
933 | $per_u = $this->totalSexUnknownPercentage(); |
||
934 | if ($tot == 0) { |
||
935 | return ''; |
||
936 | } elseif ($tot_u > 0) { |
||
937 | $chd = $this->arrayToExtendedEncoding([4095 * $tot_u / $tot, 4095 * $tot_f / $tot, 4095 * $tot_m / $tot]); |
||
938 | $chl = |
||
939 | I18N::translateContext('unknown people', 'Unknown') . ' - ' . $per_u . '|' . |
||
940 | I18N::translate('Females') . ' - ' . $per_f . '|' . |
||
941 | I18N::translate('Males') . ' - ' . $per_m; |
||
942 | $chart_title = |
||
943 | I18N::translate('Males') . ' - ' . $per_m . I18N::$list_separator . |
||
944 | I18N::translate('Females') . ' - ' . $per_f . I18N::$list_separator . |
||
945 | I18N::translateContext('unknown people', 'Unknown') . ' - ' . $per_u; |
||
946 | |||
947 | return "<img src=\"https://chart.googleapis.com/chart?cht=p3&chd=e:{$chd}&chs={$size}&chco={$color_unknown},{$color_female},{$color_male}&chf=bg,s,ffffff00&chl={$chl}\" width=\"{$sizes[0]}\" height=\"{$sizes[1]}\" alt=\"" . $chart_title . '" title="' . $chart_title . '" />'; |
||
948 | } else { |
||
949 | $chd = $this->arrayToExtendedEncoding([4095 * $tot_f / $tot, 4095 * $tot_m / $tot]); |
||
950 | $chl = |
||
951 | I18N::translate('Females') . ' - ' . $per_f . '|' . |
||
952 | I18N::translate('Males') . ' - ' . $per_m; |
||
953 | $chart_title = I18N::translate('Males') . ' - ' . $per_m . I18N::$list_separator . |
||
954 | I18N::translate('Females') . ' - ' . $per_f; |
||
955 | |||
956 | return "<img src=\"https://chart.googleapis.com/chart?cht=p3&chd=e:{$chd}&chs={$size}&chco={$color_female},{$color_male}&chf=bg,s,ffffff00&chl={$chl}\" width=\"{$sizes[0]}\" height=\"{$sizes[1]}\" alt=\"" . $chart_title . '" title="' . $chart_title . '" />'; |
||
957 | } |
||
958 | } |
||
959 | |||
960 | /** |
||
961 | * Count the number of living individuals. |
||
962 | * |
||
963 | * The totalLiving/totalDeceased queries assume that every dead person will |
||
964 | * have a DEAT record. It will not include individuals who were born more |
||
965 | * than MAX_ALIVE_AGE years ago, and who have no DEAT record. |
||
966 | * A good reason to run the “Add missing DEAT records” batch-update! |
||
967 | * |
||
968 | * @return int |
||
969 | */ |
||
970 | private function totalLivingQuery() { |
||
971 | return (int) Database::prepare( |
||
972 | "SELECT SQL_CACHE COUNT(*) FROM `##individuals` WHERE i_file = :tree_id AND i_gedcom NOT REGEXP '\\n1 (" . WT_EVENTS_DEAT . ")'" |
||
973 | )->execute([ |
||
974 | 'tree_id' => $this->tree->getTreeId(), |
||
975 | ])->fetchOne(); |
||
976 | } |
||
977 | |||
978 | /** |
||
979 | * Count the number of living individuals. |
||
980 | * |
||
981 | * @return string |
||
982 | */ |
||
983 | public function totalLiving() { |
||
984 | return I18N::number($this->totalLivingQuery()); |
||
985 | } |
||
986 | |||
987 | /** |
||
988 | * Count the number of living individuals. |
||
989 | * |
||
990 | * @return string |
||
991 | */ |
||
992 | public function totalLivingPercentage() { |
||
993 | return $this->getPercentage($this->totalLivingQuery(), 'individual'); |
||
994 | } |
||
995 | |||
996 | /** |
||
997 | * Count the number of dead individuals. |
||
998 | * |
||
999 | * @return int |
||
1000 | */ |
||
1001 | private function totalDeceasedQuery() { |
||
1002 | return (int) Database::prepare( |
||
1003 | "SELECT SQL_CACHE COUNT(*) FROM `##individuals` WHERE i_file = :tree_id AND i_gedcom REGEXP '\\n1 (" . WT_EVENTS_DEAT . ")'" |
||
1004 | )->execute([ |
||
1005 | 'tree_id' => $this->tree->getTreeId(), |
||
1006 | ])->fetchOne(); |
||
1007 | } |
||
1008 | |||
1009 | /** |
||
1010 | * Count the number of dead individuals. |
||
1011 | * |
||
1012 | * @return string |
||
1013 | */ |
||
1014 | public function totalDeceased() { |
||
1015 | return I18N::number($this->totalDeceasedQuery()); |
||
1016 | } |
||
1017 | |||
1018 | /** |
||
1019 | * Count the number of dead individuals. |
||
1020 | * |
||
1021 | * @return string |
||
1022 | */ |
||
1023 | public function totalDeceasedPercentage() { |
||
1024 | return $this->getPercentage($this->totalDeceasedQuery(), 'individual'); |
||
1025 | } |
||
1026 | |||
1027 | /** |
||
1028 | * Create a chart showing mortality. |
||
1029 | * |
||
1030 | * @param string[] $params |
||
1031 | * |
||
1032 | * @return string |
||
1033 | */ |
||
1034 | public function chartMortality($params = []) { |
||
1035 | $WT_STATS_S_CHART_X = Theme::theme()->parameter('stats-small-chart-x'); |
||
1036 | $WT_STATS_S_CHART_Y = Theme::theme()->parameter('stats-small-chart-y'); |
||
1037 | |||
1038 | if (isset($params[0]) && $params[0] != '') { |
||
1039 | $size = strtolower($params[0]); |
||
1040 | } else { |
||
1041 | $size = $WT_STATS_S_CHART_X . 'x' . $WT_STATS_S_CHART_Y; |
||
1042 | } |
||
1043 | if (isset($params[1]) && $params[1] != '') { |
||
1044 | $color_living = strtolower($params[1]); |
||
1045 | } else { |
||
1046 | $color_living = 'ffffff'; |
||
1047 | } |
||
1048 | if (isset($params[2]) && $params[2] != '') { |
||
1049 | $color_dead = strtolower($params[2]); |
||
1050 | } else { |
||
1051 | $color_dead = 'cccccc'; |
||
1052 | } |
||
1053 | $sizes = explode('x', $size); |
||
1054 | // Raw data - for calculation |
||
1055 | $tot_l = $this->totalLivingQuery(); |
||
1056 | $tot_d = $this->totalDeceasedQuery(); |
||
1057 | $tot = $tot_l + $tot_d; |
||
1058 | // I18N data - for display |
||
1059 | $per_l = $this->totalLivingPercentage(); |
||
1060 | $per_d = $this->totalDeceasedPercentage(); |
||
1061 | if ($tot == 0) { |
||
1062 | return ''; |
||
1063 | } else { |
||
1064 | $chd = $this->arrayToExtendedEncoding([4095 * $tot_l / $tot, 4095 * $tot_d / $tot]); |
||
1065 | $chl = |
||
1066 | I18N::translate('Living') . ' - ' . $per_l . '|' . |
||
1067 | I18N::translate('Dead') . ' - ' . $per_d . '|'; |
||
1068 | $chart_title = I18N::translate('Living') . ' - ' . $per_l . I18N::$list_separator . |
||
1069 | I18N::translate('Dead') . ' - ' . $per_d; |
||
1070 | |||
1071 | return "<img src=\"https://chart.googleapis.com/chart?cht=p3&chd=e:{$chd}&chs={$size}&chco={$color_living},{$color_dead}&chf=bg,s,ffffff00&chl={$chl}\" width=\"{$sizes[0]}\" height=\"{$sizes[1]}\" alt=\"" . $chart_title . '" title="' . $chart_title . '" />'; |
||
1072 | } |
||
1073 | } |
||
1074 | |||
1075 | /** |
||
1076 | * Count the number of users. |
||
1077 | * |
||
1078 | * @param string[] $params |
||
1079 | * |
||
1080 | * @return string |
||
1081 | */ |
||
1082 | public function totalUsers($params = []) { |
||
1083 | if (isset($params[0])) { |
||
1084 | $total = count(User::all()) + (int) $params[0]; |
||
1085 | } else { |
||
1086 | $total = count(User::all()); |
||
1087 | } |
||
1088 | |||
1089 | return I18N::number($total); |
||
1090 | } |
||
1091 | |||
1092 | /** |
||
1093 | * Count the number of administrators. |
||
1094 | * |
||
1095 | * @return string |
||
1096 | */ |
||
1097 | public function totalAdmins() { |
||
1098 | return I18N::number(count(User::administrators())); |
||
1099 | } |
||
1100 | |||
1101 | /** |
||
1102 | * Count the number of administrators. |
||
1103 | * |
||
1104 | * @return string |
||
1105 | */ |
||
1106 | public function totalNonAdmins() { |
||
1107 | return I18N::number(count(User::all()) - count(User::administrators())); |
||
1108 | } |
||
1109 | |||
1110 | /** |
||
1111 | * Count the number of media records with a given type. |
||
1112 | * |
||
1113 | * @param string $type |
||
1114 | * |
||
1115 | * @return int |
||
1116 | */ |
||
1117 | private function totalMediaType($type = 'all') { |
||
1118 | if (!in_array($type, $this->_media_types) && $type != 'all' && $type != 'unknown') { |
||
1119 | return 0; |
||
1120 | } |
||
1121 | $sql = "SELECT SQL_CACHE COUNT(*) AS tot FROM `##media` WHERE m_file=?"; |
||
1122 | $vars = [$this->tree->getTreeId()]; |
||
1123 | |||
1124 | if ($type != 'all') { |
||
1125 | if ($type == 'unknown') { |
||
1126 | // There has to be a better way then this :( |
||
1127 | foreach ($this->_media_types as $t) { |
||
1128 | $sql .= " AND (m_gedcom NOT LIKE ? AND m_gedcom NOT LIKE ?)"; |
||
1129 | $vars[] = "%3 TYPE {$t}%"; |
||
1130 | $vars[] = "%1 _TYPE {$t}%"; |
||
1131 | } |
||
1132 | } else { |
||
1133 | $sql .= " AND (m_gedcom LIKE ? OR m_gedcom LIKE ?)"; |
||
1134 | $vars[] = "%3 TYPE {$type}%"; |
||
1135 | $vars[] = "%1 _TYPE {$type}%"; |
||
1136 | } |
||
1137 | } |
||
1138 | |||
1139 | return (int) Database::prepare($sql)->execute($vars)->fetchOne(); |
||
1140 | } |
||
1141 | |||
1142 | /** |
||
1143 | * Count the number of media records. |
||
1144 | * |
||
1145 | * @return string |
||
1146 | */ |
||
1147 | public function totalMedia() { |
||
1148 | return I18N::number($this->totalMediaType('all')); |
||
1149 | } |
||
1150 | |||
1151 | /** |
||
1152 | * Count the number of media records with type "audio". |
||
1153 | * |
||
1154 | * @return string |
||
1155 | */ |
||
1156 | public function totalMediaAudio() { |
||
1157 | return I18N::number($this->totalMediaType('audio')); |
||
1158 | } |
||
1159 | |||
1160 | /** |
||
1161 | * Count the number of media records with type "book". |
||
1162 | * |
||
1163 | * @return string |
||
1164 | */ |
||
1165 | public function totalMediaBook() { |
||
1166 | return I18N::number($this->totalMediaType('book')); |
||
1167 | } |
||
1168 | |||
1169 | /** |
||
1170 | * Count the number of media records with type "card". |
||
1171 | * |
||
1172 | * @return string |
||
1173 | */ |
||
1174 | public function totalMediaCard() { |
||
1175 | return I18N::number($this->totalMediaType('card')); |
||
1176 | } |
||
1177 | |||
1178 | /** |
||
1179 | * Count the number of media records with type "certificate". |
||
1180 | * |
||
1181 | * @return string |
||
1182 | */ |
||
1183 | public function totalMediaCertificate() { |
||
1184 | return I18N::number($this->totalMediaType('certificate')); |
||
1185 | } |
||
1186 | |||
1187 | /** |
||
1188 | * Count the number of media records with type "coat of arms". |
||
1189 | * |
||
1190 | * @return string |
||
1191 | */ |
||
1192 | public function totalMediaCoatOfArms() { |
||
1193 | return I18N::number($this->totalMediaType('coat')); |
||
1194 | } |
||
1195 | |||
1196 | /** |
||
1197 | * Count the number of media records with type "document". |
||
1198 | * |
||
1199 | * @return string |
||
1200 | */ |
||
1201 | public function totalMediaDocument() { |
||
1202 | return I18N::number($this->totalMediaType('document')); |
||
1203 | } |
||
1204 | |||
1205 | /** |
||
1206 | * Count the number of media records with type "electronic". |
||
1207 | * |
||
1208 | * @return string |
||
1209 | */ |
||
1210 | public function totalMediaElectronic() { |
||
1211 | return I18N::number($this->totalMediaType('electronic')); |
||
1212 | } |
||
1213 | |||
1214 | /** |
||
1215 | * Count the number of media records with type "magazine". |
||
1216 | * |
||
1217 | * @return string |
||
1218 | */ |
||
1219 | public function totalMediaMagazine() { |
||
1220 | return I18N::number($this->totalMediaType('magazine')); |
||
1221 | } |
||
1222 | |||
1223 | /** |
||
1224 | * Count the number of media records with type "manuscript". |
||
1225 | * |
||
1226 | * @return string |
||
1227 | */ |
||
1228 | public function totalMediaManuscript() { |
||
1229 | return I18N::number($this->totalMediaType('manuscript')); |
||
1230 | } |
||
1231 | |||
1232 | /** |
||
1233 | * Count the number of media records with type "map". |
||
1234 | * |
||
1235 | * @return string |
||
1236 | */ |
||
1237 | public function totalMediaMap() { |
||
1238 | return I18N::number($this->totalMediaType('map')); |
||
1239 | } |
||
1240 | |||
1241 | /** |
||
1242 | * Count the number of media records with type "microfiche". |
||
1243 | * |
||
1244 | * @return string |
||
1245 | */ |
||
1246 | public function totalMediaFiche() { |
||
1247 | return I18N::number($this->totalMediaType('fiche')); |
||
1248 | } |
||
1249 | |||
1250 | /** |
||
1251 | * Count the number of media records with type "microfilm". |
||
1252 | * |
||
1253 | * @return string |
||
1254 | */ |
||
1255 | public function totalMediaFilm() { |
||
1256 | return I18N::number($this->totalMediaType('film')); |
||
1257 | } |
||
1258 | |||
1259 | /** |
||
1260 | * Count the number of media records with type "newspaper". |
||
1261 | * |
||
1262 | * @return string |
||
1263 | */ |
||
1264 | public function totalMediaNewspaper() { |
||
1265 | return I18N::number($this->totalMediaType('newspaper')); |
||
1266 | } |
||
1267 | |||
1268 | /** |
||
1269 | * Count the number of media records with type "painting". |
||
1270 | * |
||
1271 | * @return string |
||
1272 | */ |
||
1273 | public function totalMediaPainting() { |
||
1274 | return I18N::number($this->totalMediaType('painting')); |
||
1275 | } |
||
1276 | |||
1277 | /** |
||
1278 | * Count the number of media records with type "photograph". |
||
1279 | * |
||
1280 | * @return string |
||
1281 | */ |
||
1282 | public function totalMediaPhoto() { |
||
1283 | return I18N::number($this->totalMediaType('photo')); |
||
1284 | } |
||
1285 | |||
1286 | /** |
||
1287 | * Count the number of media records with type "tombstone". |
||
1288 | * |
||
1289 | * @return string |
||
1290 | */ |
||
1291 | public function totalMediaTombstone() { |
||
1292 | return I18N::number($this->totalMediaType('tombstone')); |
||
1293 | } |
||
1294 | |||
1295 | /** |
||
1296 | * Count the number of media records with type "video". |
||
1297 | * |
||
1298 | * @return string |
||
1299 | */ |
||
1300 | public function totalMediaVideo() { |
||
1301 | return I18N::number($this->totalMediaType('video')); |
||
1302 | } |
||
1303 | |||
1304 | /** |
||
1305 | * Count the number of media records with type "other". |
||
1306 | * |
||
1307 | * @return string |
||
1308 | */ |
||
1309 | public function totalMediaOther() { |
||
1310 | return I18N::number($this->totalMediaType('other')); |
||
1311 | } |
||
1312 | |||
1313 | /** |
||
1314 | * Count the number of media records with type "unknown". |
||
1315 | * |
||
1316 | * @return string |
||
1317 | */ |
||
1318 | public function totalMediaUnknown() { |
||
1319 | return I18N::number($this->totalMediaType('unknown')); |
||
1320 | } |
||
1321 | |||
1322 | /** |
||
1323 | * Create a chart of media types. |
||
1324 | * |
||
1325 | * @param string[] $params |
||
1326 | * |
||
1327 | * @return string |
||
1328 | */ |
||
1329 | public function chartMedia($params = []) { |
||
1330 | $WT_STATS_CHART_COLOR1 = Theme::theme()->parameter('distribution-chart-no-values'); |
||
1331 | $WT_STATS_CHART_COLOR2 = Theme::theme()->parameter('distribution-chart-high-values'); |
||
1332 | $WT_STATS_S_CHART_X = Theme::theme()->parameter('stats-small-chart-x'); |
||
1333 | $WT_STATS_S_CHART_Y = Theme::theme()->parameter('stats-small-chart-y'); |
||
1334 | |||
1335 | if (isset($params[0]) && $params[0] != '') { |
||
1336 | $size = strtolower($params[0]); |
||
1337 | } else { |
||
1338 | $size = $WT_STATS_S_CHART_X . 'x' . $WT_STATS_S_CHART_Y; |
||
1339 | } |
||
1340 | if (isset($params[1]) && $params[1] != '') { |
||
1341 | $color_from = strtolower($params[1]); |
||
1342 | } else { |
||
1343 | $color_from = $WT_STATS_CHART_COLOR1; |
||
1344 | } |
||
1345 | if (isset($params[2]) && $params[2] != '') { |
||
1346 | $color_to = strtolower($params[2]); |
||
1347 | } else { |
||
1348 | $color_to = $WT_STATS_CHART_COLOR2; |
||
1349 | } |
||
1350 | $sizes = explode('x', $size); |
||
1351 | $tot = $this->totalMediaType('all'); |
||
1352 | // Beware divide by zero |
||
1353 | if ($tot == 0) { |
||
1354 | return I18N::translate('None'); |
||
1355 | } |
||
1356 | // Build a table listing only the media types actually present in the GEDCOM |
||
1357 | $mediaCounts = []; |
||
1358 | $mediaTypes = ''; |
||
1359 | $chart_title = ''; |
||
1360 | $c = 0; |
||
1361 | $max = 0; |
||
1362 | $media = []; |
||
1363 | foreach ($this->_media_types as $type) { |
||
1364 | $count = $this->totalMediaType($type); |
||
1365 | if ($count > 0) { |
||
1366 | $media[$type] = $count; |
||
1367 | if ($count > $max) { |
||
1368 | $max = $count; |
||
1369 | } |
||
1370 | $c += $count; |
||
1371 | } |
||
1372 | } |
||
1373 | $count = $this->totalMediaType('unknown'); |
||
1374 | if ($count > 0) { |
||
1375 | $media['unknown'] = $tot - $c; |
||
1376 | if ($tot - $c > $max) { |
||
1377 | $max = $count; |
||
1378 | } |
||
1379 | } |
||
1380 | if (($max / $tot) > 0.6 && count($media) > 10) { |
||
1381 | arsort($media); |
||
1382 | $media = array_slice($media, 0, 10); |
||
1383 | $c = $tot; |
||
1384 | foreach ($media as $cm) { |
||
1385 | $c -= $cm; |
||
1386 | } |
||
1387 | if (isset($media['other'])) { |
||
1388 | $media['other'] += $c; |
||
1389 | } else { |
||
1390 | $media['other'] = $c; |
||
1391 | } |
||
1392 | } |
||
1393 | asort($media); |
||
1394 | foreach ($media as $type => $count) { |
||
1395 | $mediaCounts[] = round(100 * $count / $tot, 0); |
||
1396 | $mediaTypes .= GedcomTag::getFileFormTypeValue($type) . ' - ' . I18N::number($count) . '|'; |
||
1397 | $chart_title .= GedcomTag::getFileFormTypeValue($type) . ' (' . $count . '), '; |
||
1398 | } |
||
1399 | $chart_title = substr($chart_title, 0, -2); |
||
1400 | $chd = $this->arrayToExtendedEncoding($mediaCounts); |
||
1401 | $chl = substr($mediaTypes, 0, -1); |
||
1402 | |||
1403 | return "<img src=\"https://chart.googleapis.com/chart?cht=p3&chd=e:{$chd}&chs={$size}&chco={$color_from},{$color_to}&chf=bg,s,ffffff00&chl={$chl}\" width=\"{$sizes[0]}\" height=\"{$sizes[1]}\" alt=\"" . $chart_title . '" title="' . $chart_title . '" />'; |
||
1404 | } |
||
1405 | |||
1406 | /** |
||
1407 | * Birth and Death |
||
1408 | * |
||
1409 | * @param string $type |
||
1410 | * @param string $life_dir |
||
1411 | * @param string $birth_death |
||
1412 | * |
||
1413 | * @return string |
||
1414 | */ |
||
1415 | private function mortalityQuery($type = 'full', $life_dir = 'ASC', $birth_death = 'BIRT') { |
||
1416 | if ($birth_death == 'MARR') { |
||
1417 | $query_field = "'MARR'"; |
||
1418 | } elseif ($birth_death == 'DIV') { |
||
1419 | $query_field = "'DIV'"; |
||
1420 | } elseif ($birth_death == 'BIRT') { |
||
1421 | $query_field = "'BIRT'"; |
||
1422 | } else { |
||
1423 | $query_field = "'DEAT'"; |
||
1424 | } |
||
1425 | if ($life_dir == 'ASC') { |
||
1426 | $dmod = 'MIN'; |
||
1427 | } else { |
||
1428 | $dmod = 'MAX'; |
||
1429 | } |
||
1430 | $rows = $this->runSql( |
||
1431 | "SELECT SQL_CACHE d_year, d_type, d_fact, d_gid" . |
||
1432 | " FROM `##dates`" . |
||
1433 | " WHERE d_file={$this->tree->getTreeId()} AND d_fact IN ({$query_field}) AND d_julianday1=(" . |
||
1434 | " SELECT {$dmod}( d_julianday1 )" . |
||
1435 | " FROM `##dates`" . |
||
1436 | " WHERE d_file={$this->tree->getTreeId()} AND d_fact IN ({$query_field}) AND d_julianday1<>0 )" . |
||
1437 | " LIMIT 1" |
||
1438 | ); |
||
1439 | if (!isset($rows[0])) { |
||
1440 | return ''; |
||
1441 | } |
||
1442 | $row = $rows[0]; |
||
1443 | $record = GedcomRecord::getInstance($row['d_gid'], $this->tree); |
||
1444 | switch ($type) { |
||
1445 | default: |
||
1446 | case 'full': |
||
1447 | if ($record->canShow()) { |
||
1448 | $result = $record->formatList(); |
||
1449 | } else { |
||
1450 | $result = I18N::translate('This information is private and cannot be shown.'); |
||
1451 | } |
||
1452 | break; |
||
1453 | case 'year': |
||
1454 | if ($row['d_year'] < 0) { |
||
1455 | $row['d_year'] = abs($row['d_year']) . ' B.C.'; |
||
1456 | } |
||
1457 | $date = new Date($row['d_type'] . ' ' . $row['d_year']); |
||
1458 | $result = $date->display(); |
||
1459 | break; |
||
1460 | case 'name': |
||
1461 | $result = '<a href="' . e($record->url()) . '">' . $record->getFullName() . '</a>'; |
||
1462 | break; |
||
1463 | case 'place': |
||
1464 | $fact = GedcomRecord::getInstance($row['d_gid'], $this->tree)->getFirstFact($row['d_fact']); |
||
1465 | if ($fact) { |
||
1466 | $result = FunctionsPrint::formatFactPlace($fact, true, true, true); |
||
1467 | } else { |
||
1468 | $result = I18N::translate('Private'); |
||
1469 | } |
||
1470 | break; |
||
1471 | } |
||
1472 | |||
1473 | return $result; |
||
1474 | } |
||
1475 | |||
1476 | /** |
||
1477 | * Places |
||
1478 | * |
||
1479 | * @param string $what |
||
1480 | * @param string $fact |
||
1481 | * @param int $parent |
||
1482 | * @param bool $country |
||
1483 | * |
||
1484 | * @return int[]|string[][] |
||
1485 | */ |
||
1486 | public function statsPlaces($what = 'ALL', $fact = '', $parent = 0, $country = false) { |
||
1487 | if ($fact) { |
||
1488 | if ($what == 'INDI') { |
||
1489 | $rows = Database::prepare( |
||
1490 | "SELECT i_gedcom AS ged FROM `##individuals` WHERE i_file = :tree_id AND i_gedcom LIKE '%\n2 PLAC %'" |
||
1491 | )->execute([ |
||
1492 | 'tree_id' => $this->tree->getTreeId(), |
||
1493 | ])->fetchAll(); |
||
1494 | } elseif ($what == 'FAM') { |
||
1495 | $rows = Database::prepare( |
||
1496 | "SELECT f_gedcom AS ged FROM `##families` WHERE f_file = :tree_id AND f_gedcom LIKE '%\n2 PLAC %'" |
||
1497 | )->execute([ |
||
1498 | 'tree_id' => $this->tree->getTreeId(), |
||
1499 | ])->fetchAll(); |
||
1500 | } |
||
1501 | $placelist = []; |
||
1502 | foreach ($rows as $row) { |
||
1503 | if (preg_match('/\n1 ' . $fact . '(?:\n[2-9].*)*\n2 PLAC (.+)/', $row->ged, $match)) { |
||
1504 | if ($country) { |
||
1505 | $tmp = explode(Place::GEDCOM_SEPARATOR, $match[1]); |
||
1506 | $place = end($tmp); |
||
1507 | } else { |
||
1508 | $place = $match[1]; |
||
1509 | } |
||
1510 | if (!isset($placelist[$place])) { |
||
1511 | $placelist[$place] = 1; |
||
1512 | } else { |
||
1513 | $placelist[$place]++; |
||
1514 | } |
||
1515 | } |
||
1516 | } |
||
1517 | |||
1518 | return $placelist; |
||
1519 | } elseif ($parent > 0) { |
||
1520 | // used by placehierarchy googlemap module |
||
1521 | if ($what == 'INDI') { |
||
1522 | $join = " JOIN `##individuals` ON pl_file = i_file AND pl_gid = i_id"; |
||
1523 | } elseif ($what == 'FAM') { |
||
1524 | $join = " JOIN `##families` ON pl_file = f_file AND pl_gid = f_id"; |
||
1525 | } else { |
||
1526 | $join = ""; |
||
1527 | } |
||
1528 | $rows = $this->runSql( |
||
1529 | " SELECT SQL_CACHE" . |
||
1530 | " p_place AS place," . |
||
1531 | " COUNT(*) AS tot" . |
||
1532 | " FROM" . |
||
1533 | " `##places`" . |
||
1534 | " JOIN `##placelinks` ON pl_file=p_file AND p_id=pl_p_id" . |
||
1535 | $join . |
||
1536 | " WHERE" . |
||
1537 | " p_id={$parent} AND" . |
||
1538 | " p_file={$this->tree->getTreeId()}" . |
||
1539 | " GROUP BY place" |
||
1540 | ); |
||
1541 | |||
1542 | return $rows; |
||
1543 | } else { |
||
1544 | if ($what == 'INDI') { |
||
1545 | $join = " JOIN `##individuals` ON pl_file = i_file AND pl_gid = i_id"; |
||
1546 | } elseif ($what == 'FAM') { |
||
1547 | $join = " JOIN `##families` ON pl_file = f_file AND pl_gid = f_id"; |
||
1548 | } else { |
||
1549 | $join = ""; |
||
1550 | } |
||
1551 | $rows = $this->runSql( |
||
1552 | " SELECT SQL_CACHE" . |
||
1553 | " p_place AS country," . |
||
1554 | " COUNT(*) AS tot" . |
||
1555 | " FROM" . |
||
1556 | " `##places`" . |
||
1557 | " JOIN `##placelinks` ON pl_file=p_file AND p_id=pl_p_id" . |
||
1558 | $join . |
||
1559 | " WHERE" . |
||
1560 | " p_file={$this->tree->getTreeId()}" . |
||
1561 | " AND p_parent_id='0'" . |
||
1562 | " GROUP BY country ORDER BY tot DESC, country ASC" |
||
1563 | ); |
||
1564 | |||
1565 | return $rows; |
||
1566 | } |
||
1567 | } |
||
1568 | |||
1569 | /** |
||
1570 | * Count total places. |
||
1571 | * |
||
1572 | * @return int |
||
1573 | */ |
||
1574 | private function totalPlacesQuery() { |
||
1575 | return |
||
1576 | (int) Database::prepare("SELECT SQL_CACHE COUNT(*) FROM `##places` WHERE p_file=?") |
||
1577 | ->execute([$this->tree->getTreeId()]) |
||
1578 | ->fetchOne(); |
||
1579 | } |
||
1580 | |||
1581 | /** |
||
1582 | * Count total places. |
||
1583 | * |
||
1584 | * @return string |
||
1585 | */ |
||
1586 | public function totalPlaces() { |
||
1587 | return I18N::number($this->totalPlacesQuery()); |
||
1588 | } |
||
1589 | |||
1590 | /** |
||
1591 | * Create a chart showing where events occurred. |
||
1592 | * |
||
1593 | * @param string[] $params |
||
1594 | * |
||
1595 | * @return string |
||
1596 | */ |
||
1597 | public function chartDistribution($params = []) { |
||
1598 | $WT_STATS_CHART_COLOR1 = Theme::theme()->parameter('distribution-chart-no-values'); |
||
1599 | $WT_STATS_CHART_COLOR2 = Theme::theme()->parameter('distribution-chart-high-values'); |
||
1600 | $WT_STATS_CHART_COLOR3 = Theme::theme()->parameter('distribution-chart-low-values'); |
||
1601 | $WT_STATS_MAP_X = Theme::theme()->parameter('distribution-chart-x'); |
||
1602 | $WT_STATS_MAP_Y = Theme::theme()->parameter('distribution-chart-y'); |
||
1603 | |||
1604 | if (isset($params[0])) { |
||
1605 | $chart_shows = $params[0]; |
||
1606 | } else { |
||
1607 | $chart_shows = 'world'; |
||
1608 | } |
||
1609 | if (isset($params[1])) { |
||
1610 | $chart_type = $params[1]; |
||
1611 | } else { |
||
1612 | $chart_type = ''; |
||
1613 | } |
||
1614 | if (isset($params[2])) { |
||
1615 | $surname = $params[2]; |
||
1616 | } else { |
||
1617 | $surname = ''; |
||
1618 | } |
||
1619 | |||
1620 | if ($this->totalPlacesQuery() == 0) { |
||
1621 | return ''; |
||
1622 | } |
||
1623 | // Get the country names for each language |
||
1624 | $country_to_iso3166 = []; |
||
1625 | foreach (I18N::activeLocales() as $locale) { |
||
1626 | I18N::init($locale->languageTag()); |
||
1627 | $countries = $this->getAllCountries(); |
||
1628 | foreach ($this->iso3166() as $three => $two) { |
||
1629 | $country_to_iso3166[$three] = $two; |
||
1630 | $country_to_iso3166[$countries[$three]] = $two; |
||
1631 | } |
||
1632 | } |
||
1633 | I18N::init(WT_LOCALE); |
||
1634 | switch ($chart_type) { |
||
1635 | case 'surname_distribution_chart': |
||
1636 | if ($surname == '') { |
||
1637 | $surname = $this->getCommonSurname(); |
||
1638 | } |
||
1639 | $chart_title = I18N::translate('Surname distribution chart') . ': ' . $surname; |
||
1640 | // Count how many people are events in each country |
||
1641 | $surn_countries = []; |
||
1642 | $indis = QueryName::individuals($this->tree, I18N::strtoupper($surname), '', '', false, false); |
||
1643 | foreach ($indis as $person) { |
||
1644 | if (preg_match_all('/^2 PLAC (?:.*, *)*(.*)/m', $person->getGedcom(), $matches)) { |
||
1645 | // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes. |
||
1646 | foreach ($matches[1] as $country) { |
||
1647 | if (array_key_exists($country, $country_to_iso3166)) { |
||
1648 | if (array_key_exists($country_to_iso3166[$country], $surn_countries)) { |
||
1649 | $surn_countries[$country_to_iso3166[$country]]++; |
||
1650 | } else { |
||
1651 | $surn_countries[$country_to_iso3166[$country]] = 1; |
||
1652 | } |
||
1653 | } |
||
1654 | } |
||
1655 | } |
||
1656 | } |
||
1657 | break; |
||
1658 | case 'birth_distribution_chart': |
||
1659 | $chart_title = I18N::translate('Birth by country'); |
||
1660 | // Count how many people were born in each country |
||
1661 | $surn_countries = []; |
||
1662 | $b_countries = $this->statsPlaces('INDI', 'BIRT', 0, true); |
||
1663 | foreach ($b_countries as $place => $count) { |
||
1664 | $country = $place; |
||
1665 | if (array_key_exists($country, $country_to_iso3166)) { |
||
1666 | if (!isset($surn_countries[$country_to_iso3166[$country]])) { |
||
1667 | $surn_countries[$country_to_iso3166[$country]] = $count; |
||
1668 | } else { |
||
1669 | $surn_countries[$country_to_iso3166[$country]] += $count; |
||
1670 | } |
||
1671 | } |
||
1672 | } |
||
1673 | break; |
||
1674 | case 'death_distribution_chart': |
||
1675 | $chart_title = I18N::translate('Death by country'); |
||
1676 | // Count how many people were death in each country |
||
1677 | $surn_countries = []; |
||
1678 | $d_countries = $this->statsPlaces('INDI', 'DEAT', 0, true); |
||
1679 | foreach ($d_countries as $place => $count) { |
||
1680 | $country = $place; |
||
1681 | if (array_key_exists($country, $country_to_iso3166)) { |
||
1682 | if (!isset($surn_countries[$country_to_iso3166[$country]])) { |
||
1683 | $surn_countries[$country_to_iso3166[$country]] = $count; |
||
1684 | } else { |
||
1685 | $surn_countries[$country_to_iso3166[$country]] += $count; |
||
1686 | } |
||
1687 | } |
||
1688 | } |
||
1689 | break; |
||
1690 | case 'marriage_distribution_chart': |
||
1691 | $chart_title = I18N::translate('Marriage by country'); |
||
1692 | // Count how many families got marriage in each country |
||
1693 | $surn_countries = []; |
||
1694 | $m_countries = $this->statsPlaces('FAM'); |
||
1695 | // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes. |
||
1696 | foreach ($m_countries as $place) { |
||
1697 | $country = $place['country']; |
||
1698 | if (array_key_exists($country, $country_to_iso3166)) { |
||
1699 | if (!isset($surn_countries[$country_to_iso3166[$country]])) { |
||
1700 | $surn_countries[$country_to_iso3166[$country]] = $place['tot']; |
||
1701 | } else { |
||
1702 | $surn_countries[$country_to_iso3166[$country]] += $place['tot']; |
||
1703 | } |
||
1704 | } |
||
1705 | } |
||
1706 | break; |
||
1707 | case 'indi_distribution_chart': |
||
1708 | default: |
||
1709 | $chart_title = I18N::translate('Individual distribution chart'); |
||
1710 | // Count how many people have events in each country |
||
1711 | $surn_countries = []; |
||
1712 | $a_countries = $this->statsPlaces('INDI'); |
||
1713 | // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes. |
||
1714 | foreach ($a_countries as $place) { |
||
1715 | $country = $place['country']; |
||
1716 | if (array_key_exists($country, $country_to_iso3166)) { |
||
1717 | if (!isset($surn_countries[$country_to_iso3166[$country]])) { |
||
1718 | $surn_countries[$country_to_iso3166[$country]] = $place['tot']; |
||
1719 | } else { |
||
1720 | $surn_countries[$country_to_iso3166[$country]] += $place['tot']; |
||
1721 | } |
||
1722 | } |
||
1723 | } |
||
1724 | break; |
||
1725 | } |
||
1726 | $chart_url = 'https://chart.googleapis.com/chart?cht=t&chtm=' . $chart_shows; |
||
1727 | $chart_url .= '&chco=' . $WT_STATS_CHART_COLOR1 . ',' . $WT_STATS_CHART_COLOR3 . ',' . $WT_STATS_CHART_COLOR2; // country colours |
||
1728 | $chart_url .= '&chf=bg,s,ECF5FF'; // sea colour |
||
1729 | $chart_url .= '&chs=' . $WT_STATS_MAP_X . 'x' . $WT_STATS_MAP_Y; |
||
1730 | $chart_url .= '&chld=' . implode('', array_keys($surn_countries)) . '&chd=s:'; |
||
1731 | foreach ($surn_countries as $count) { |
||
1732 | $chart_url .= substr(WT_GOOGLE_CHART_ENCODING, (int) ($count / max($surn_countries) * 61), 1); |
||
1733 | } |
||
1734 | $chart = '<div id="google_charts" class="center">'; |
||
1735 | $chart .= '<p>' . $chart_title . '</p>'; |
||
1736 | $chart .= '<div><img src="' . $chart_url . '" alt="' . $chart_title . '" title="' . $chart_title . '" class="gchart" /><br>'; |
||
1737 | $chart .= '<table class="center"><tr>'; |
||
1738 | $chart .= '<td bgcolor="#' . $WT_STATS_CHART_COLOR2 . '" width="12"></td><td>' . I18N::translate('Highest population') . '</td>'; |
||
1739 | $chart .= '<td bgcolor="#' . $WT_STATS_CHART_COLOR3 . '" width="12"></td><td>' . I18N::translate('Lowest population') . '</td>'; |
||
1740 | $chart .= '<td bgcolor="#' . $WT_STATS_CHART_COLOR1 . '" width="12"></td><td>' . I18N::translate('Nobody at all') . '</td>'; |
||
1741 | $chart .= '</tr></table></div></div>'; |
||
1742 | |||
1743 | return $chart; |
||
1744 | } |
||
1745 | |||
1746 | /** |
||
1747 | * A list of common countries. |
||
1748 | * |
||
1749 | * @return string |
||
1750 | */ |
||
1751 | public function commonCountriesList() { |
||
1752 | $countries = $this->statsPlaces(); |
||
1753 | if (empty($countries)) { |
||
1754 | return ''; |
||
1755 | } |
||
1756 | $top10 = []; |
||
1757 | $i = 1; |
||
1758 | // Get the country names for each language |
||
1759 | $country_names = []; |
||
1760 | foreach (I18N::activeLocales() as $locale) { |
||
1761 | I18N::init($locale->languageTag()); |
||
1762 | $all_countries = $this->getAllCountries(); |
||
1763 | foreach ($all_countries as $country_code => $country_name) { |
||
1764 | $country_names[$country_name] = $country_code; |
||
1765 | } |
||
1766 | } |
||
1767 | I18N::init(WT_LOCALE); |
||
1768 | $all_db_countries = []; |
||
1769 | foreach ($countries as $place) { |
||
1770 | $country = trim($place['country']); |
||
1771 | if (array_key_exists($country, $country_names)) { |
||
1772 | if (!isset($all_db_countries[$country_names[$country]][$country])) { |
||
1773 | $all_db_countries[$country_names[$country]][$country] = (int) $place['tot']; |
||
1774 | } else { |
||
1775 | $all_db_countries[$country_names[$country]][$country] += (int) $place['tot']; |
||
1776 | } |
||
1777 | } |
||
1778 | } |
||
1779 | // get all the user’s countries names |
||
1780 | $all_countries = $this->getAllCountries(); |
||
1781 | foreach ($all_db_countries as $country_code => $country) { |
||
1782 | $top10[] = '<li>'; |
||
1783 | foreach ($country as $country_name => $tot) { |
||
1784 | $tmp = new Place($country_name, $this->tree); |
||
1785 | $place = '<a href="' . $tmp->getURL() . '" class="list_item">' . $all_countries[$country_code] . '</a>'; |
||
1786 | $top10[] .= $place . ' - ' . I18N::number($tot); |
||
1787 | } |
||
1788 | $top10[] .= '</li>'; |
||
1789 | if ($i++ == 10) { |
||
1790 | break; |
||
1791 | } |
||
1792 | } |
||
1793 | $top10 = implode('', $top10); |
||
1794 | |||
1795 | return '<ul>' . $top10 . '</ul>'; |
||
1796 | } |
||
1797 | |||
1798 | /** |
||
1799 | * A list of common birth places. |
||
1800 | * |
||
1801 | * @return string |
||
1802 | */ |
||
1803 | public function commonBirthPlacesList() { |
||
1804 | $places = $this->statsPlaces('INDI', 'BIRT'); |
||
1805 | $top10 = []; |
||
1806 | $i = 1; |
||
1807 | arsort($places); |
||
1808 | foreach ($places as $place => $count) { |
||
1809 | $tmp = new Place($place, $this->tree); |
||
1810 | $place = '<a href="' . $tmp->getURL() . '" class="list_item">' . $tmp->getFullName() . '</a>'; |
||
1811 | $top10[] = '<li>' . $place . ' - ' . I18N::number($count) . '</li>'; |
||
1812 | if ($i++ == 10) { |
||
1813 | break; |
||
1814 | } |
||
1815 | } |
||
1816 | $top10 = implode('', $top10); |
||
1817 | |||
1818 | return '<ul>' . $top10 . '</ul>'; |
||
1819 | } |
||
1820 | |||
1821 | /** |
||
1822 | * A list of common death places. |
||
1823 | * |
||
1824 | * @return string |
||
1825 | */ |
||
1826 | public function commonDeathPlacesList() { |
||
1827 | $places = $this->statsPlaces('INDI', 'DEAT'); |
||
1828 | $top10 = []; |
||
1829 | $i = 1; |
||
1830 | arsort($places); |
||
1831 | foreach ($places as $place => $count) { |
||
1832 | $tmp = new Place($place, $this->tree); |
||
1833 | $place = '<a href="' . $tmp->getURL() . '" class="list_item">' . $tmp->getFullName() . '</a>'; |
||
1834 | $top10[] = '<li>' . $place . ' - ' . I18N::number($count) . '</li>'; |
||
1835 | if ($i++ == 10) { |
||
1836 | break; |
||
1837 | } |
||
1838 | } |
||
1839 | $top10 = implode('', $top10); |
||
1840 | |||
1841 | return '<ul>' . $top10 . '</ul>'; |
||
1842 | } |
||
1843 | |||
1844 | /** |
||
1845 | * A list of common marriage places. |
||
1846 | * |
||
1847 | * @return string |
||
1848 | */ |
||
1849 | public function commonMarriagePlacesList() { |
||
1850 | $places = $this->statsPlaces('FAM', 'MARR'); |
||
1851 | $top10 = []; |
||
1852 | $i = 1; |
||
1853 | arsort($places); |
||
1854 | foreach ($places as $place => $count) { |
||
1855 | $tmp = new Place($place, $this->tree); |
||
1856 | $place = '<a href="' . $tmp->getURL() . '" class="list_item">' . $tmp->getFullName() . '</a>'; |
||
1857 | $top10[] = '<li>' . $place . ' - ' . I18N::number($count) . '</li>'; |
||
1858 | if ($i++ == 10) { |
||
1859 | break; |
||
1860 | } |
||
1861 | } |
||
1862 | $top10 = implode('', $top10); |
||
1863 | |||
1864 | return '<ul>' . $top10 . '</ul>'; |
||
1865 | } |
||
1866 | |||
1867 | /** |
||
1868 | * Create a chart of birth places. |
||
1869 | * |
||
1870 | * @param bool $simple |
||
1871 | * @param bool $sex |
||
1872 | * @param int $year1 |
||
1873 | * @param int $year2 |
||
1874 | * @param string[] $params |
||
1875 | * |
||
1876 | * @return array|string |
||
1877 | */ |
||
1878 | public function statsBirthQuery($simple = true, $sex = false, $year1 = -1, $year2 = -1, $params = []) { |
||
1879 | $WT_STATS_CHART_COLOR1 = Theme::theme()->parameter('distribution-chart-no-values'); |
||
1880 | $WT_STATS_CHART_COLOR2 = Theme::theme()->parameter('distribution-chart-high-values'); |
||
1881 | $WT_STATS_S_CHART_X = Theme::theme()->parameter('stats-small-chart-x'); |
||
1882 | $WT_STATS_S_CHART_Y = Theme::theme()->parameter('stats-small-chart-y'); |
||
1883 | |||
1884 | if ($simple) { |
||
1885 | $sql = |
||
1886 | "SELECT SQL_CACHE FLOOR(d_year/100+1) AS century, COUNT(*) AS total FROM `##dates` " . |
||
1887 | "WHERE " . |
||
1888 | "d_file = {$this->tree->getTreeId()} AND " . |
||
1889 | "d_year <> 0 AND " . |
||
1890 | "d_fact='BIRT' AND " . |
||
1891 | "d_type IN ('@#DGREGORIAN@', '@#DJULIAN@')"; |
||
1892 | } elseif ($sex) { |
||
1893 | $sql = |
||
1894 | "SELECT SQL_CACHE d_month, i_sex, COUNT(*) AS total FROM `##dates` " . |
||
1895 | "JOIN `##individuals` ON d_file = i_file AND d_gid = i_id " . |
||
1896 | "WHERE " . |
||
1897 | "d_file={$this->tree->getTreeId()} AND " . |
||
1898 | "d_fact='BIRT' AND " . |
||
1899 | "d_type IN ('@#DGREGORIAN@', '@#DJULIAN@')"; |
||
1900 | } else { |
||
1901 | $sql = |
||
1902 | "SELECT SQL_CACHE d_month, COUNT(*) AS total FROM `##dates` " . |
||
1903 | "WHERE " . |
||
1904 | "d_file={$this->tree->getTreeId()} AND " . |
||
1905 | "d_fact='BIRT' AND " . |
||
1906 | "d_type IN ('@#DGREGORIAN@', '@#DJULIAN@')"; |
||
1907 | } |
||
1908 | if ($year1 >= 0 && $year2 >= 0) { |
||
1909 | $sql .= " AND d_year BETWEEN '{$year1}' AND '{$year2}'"; |
||
1910 | } |
||
1911 | if ($simple) { |
||
1912 | $sql .= " GROUP BY century ORDER BY century"; |
||
1913 | } else { |
||
1914 | $sql .= " GROUP BY d_month"; |
||
1915 | if ($sex) { |
||
1916 | $sql .= ", i_sex"; |
||
1917 | } |
||
1918 | } |
||
1919 | $rows = $this->runSql($sql); |
||
1920 | if ($simple) { |
||
1921 | if (isset($params[0]) && $params[0] != '') { |
||
1922 | $size = strtolower($params[0]); |
||
1923 | } else { |
||
1924 | $size = $WT_STATS_S_CHART_X . 'x' . $WT_STATS_S_CHART_Y; |
||
1925 | } |
||
1926 | if (isset($params[1]) && $params[1] != '') { |
||
1927 | $color_from = strtolower($params[1]); |
||
1928 | } else { |
||
1929 | $color_from = $WT_STATS_CHART_COLOR1; |
||
1930 | } |
||
1931 | if (isset($params[2]) && $params[2] != '') { |
||
1932 | $color_to = strtolower($params[2]); |
||
1933 | } else { |
||
1934 | $color_to = $WT_STATS_CHART_COLOR2; |
||
1935 | } |
||
1936 | $sizes = explode('x', $size); |
||
1937 | $tot = 0; |
||
1938 | foreach ($rows as $values) { |
||
1939 | $tot += $values['total']; |
||
1940 | } |
||
1941 | // Beware divide by zero |
||
1942 | if ($tot == 0) { |
||
1943 | return ''; |
||
1944 | } |
||
1945 | $centuries = ''; |
||
1946 | $counts = []; |
||
1947 | foreach ($rows as $values) { |
||
1948 | $counts[] = round(100 * $values['total'] / $tot, 0); |
||
1949 | $centuries .= $this->centuryName($values['century']) . ' - ' . I18N::number($values['total']) . '|'; |
||
1950 | } |
||
1951 | $chd = $this->arrayToExtendedEncoding($counts); |
||
1952 | $chl = rawurlencode(substr($centuries, 0, -1)); |
||
1953 | |||
1954 | return "<img src=\"https://chart.googleapis.com/chart?cht=p3&chd=e:{$chd}&chs={$size}&chco={$color_from},{$color_to}&chf=bg,s,ffffff00&chl={$chl}\" width=\"{$sizes[0]}\" height=\"{$sizes[1]}\" alt=\"" . I18N::translate('Births by century') . '" title="' . I18N::translate('Births by century') . '" />'; |
||
1955 | } else { |
||
1956 | return $rows; |
||
1957 | } |
||
1958 | } |
||
1959 | |||
1960 | /** |
||
1961 | * Create a chart of death places. |
||
1962 | * |
||
1963 | * @param bool $simple |
||
1964 | * @param bool $sex |
||
1965 | * @param int $year1 |
||
1966 | * @param int $year2 |
||
1967 | * @param string[] $params |
||
1968 | * |
||
1969 | * @return array|string |
||
1970 | */ |
||
1971 | public function statsDeathQuery($simple = true, $sex = false, $year1 = -1, $year2 = -1, $params = []) { |
||
1972 | $WT_STATS_CHART_COLOR1 = Theme::theme()->parameter('distribution-chart-no-values'); |
||
1973 | $WT_STATS_CHART_COLOR2 = Theme::theme()->parameter('distribution-chart-high-values'); |
||
1974 | $WT_STATS_S_CHART_X = Theme::theme()->parameter('stats-small-chart-x'); |
||
1975 | $WT_STATS_S_CHART_Y = Theme::theme()->parameter('stats-small-chart-y'); |
||
1976 | |||
1977 | if ($simple) { |
||
1978 | $sql = |
||
1979 | "SELECT SQL_CACHE FLOOR(d_year/100+1) AS century, COUNT(*) AS total FROM `##dates` " . |
||
1980 | "WHERE " . |
||
1981 | "d_file={$this->tree->getTreeId()} AND " . |
||
1982 | 'd_year<>0 AND ' . |
||
1983 | "d_fact='DEAT' AND " . |
||
1984 | "d_type IN ('@#DGREGORIAN@', '@#DJULIAN@')"; |
||
1985 | } elseif ($sex) { |
||
1986 | $sql = |
||
1987 | "SELECT SQL_CACHE d_month, i_sex, COUNT(*) AS total FROM `##dates` " . |
||
1988 | "JOIN `##individuals` ON d_file = i_file AND d_gid = i_id " . |
||
1989 | "WHERE " . |
||
1990 | "d_file={$this->tree->getTreeId()} AND " . |
||
1991 | "d_fact='DEAT' AND " . |
||
1992 | "d_type IN ('@#DGREGORIAN@', '@#DJULIAN@')"; |
||
1993 | } else { |
||
1994 | $sql = |
||
1995 | "SELECT SQL_CACHE d_month, COUNT(*) AS total FROM `##dates` " . |
||
1996 | "WHERE " . |
||
1997 | "d_file={$this->tree->getTreeId()} AND " . |
||
1998 | "d_fact='DEAT' AND " . |
||
1999 | "d_type IN ('@#DGREGORIAN@', '@#DJULIAN@')"; |
||
2000 | } |
||
2001 | if ($year1 >= 0 && $year2 >= 0) { |
||
2002 | $sql .= " AND d_year BETWEEN '{$year1}' AND '{$year2}'"; |
||
2003 | } |
||
2004 | if ($simple) { |
||
2005 | $sql .= " GROUP BY century ORDER BY century"; |
||
2006 | } else { |
||
2007 | $sql .= " GROUP BY d_month"; |
||
2008 | if ($sex) { |
||
2009 | $sql .= ", i_sex"; |
||
2010 | } |
||
2011 | } |
||
2012 | $rows = $this->runSql($sql); |
||
2013 | if ($simple) { |
||
2014 | if (isset($params[0]) && $params[0] != '') { |
||
2015 | $size = strtolower($params[0]); |
||
2016 | } else { |
||
2017 | $size = $WT_STATS_S_CHART_X . 'x' . $WT_STATS_S_CHART_Y; |
||
2018 | } |
||
2019 | if (isset($params[1]) && $params[1] != '') { |
||
2020 | $color_from = strtolower($params[1]); |
||
2021 | } else { |
||
2022 | $color_from = $WT_STATS_CHART_COLOR1; |
||
2023 | } |
||
2024 | if (isset($params[2]) && $params[2] != '') { |
||
2025 | $color_to = strtolower($params[2]); |
||
2026 | } else { |
||
2027 | $color_to = $WT_STATS_CHART_COLOR2; |
||
2028 | } |
||
2029 | $sizes = explode('x', $size); |
||
2030 | $tot = 0; |
||
2031 | foreach ($rows as $values) { |
||
2032 | $tot += $values['total']; |
||
2033 | } |
||
2034 | // Beware divide by zero |
||
2035 | if ($tot == 0) { |
||
2036 | return ''; |
||
2037 | } |
||
2038 | $centuries = ''; |
||
2039 | $counts = []; |
||
2040 | foreach ($rows as $values) { |
||
2041 | $counts[] = round(100 * $values['total'] / $tot, 0); |
||
2042 | $centuries .= $this->centuryName($values['century']) . ' - ' . I18N::number($values['total']) . '|'; |
||
2043 | } |
||
2044 | $chd = $this->arrayToExtendedEncoding($counts); |
||
2045 | $chl = rawurlencode(substr($centuries, 0, -1)); |
||
2046 | |||
2047 | return "<img src=\"https://chart.googleapis.com/chart?cht=p3&chd=e:{$chd}&chs={$size}&chco={$color_from},{$color_to}&chf=bg,s,ffffff00&chl={$chl}\" width=\"{$sizes[0]}\" height=\"{$sizes[1]}\" alt=\"" . I18N::translate('Deaths by century') . '" title="' . I18N::translate('Deaths by century') . '" />'; |
||
2048 | } |
||
2049 | |||
2050 | return $rows; |
||
2051 | } |
||
2052 | |||
2053 | /** |
||
2054 | * Find the earliest birth. |
||
2055 | * |
||
2056 | * @return string |
||
2057 | */ |
||
2058 | public function firstBirth() { |
||
2059 | return $this->mortalityQuery('full', 'ASC', 'BIRT'); |
||
2060 | } |
||
2061 | |||
2062 | /** |
||
2063 | * Find the earliest birth year. |
||
2064 | * |
||
2065 | * @return string |
||
2066 | */ |
||
2067 | public function firstBirthYear() { |
||
2068 | return $this->mortalityQuery('year', 'ASC', 'BIRT'); |
||
2069 | } |
||
2070 | |||
2071 | /** |
||
2072 | * Find the name of the earliest birth. |
||
2073 | * |
||
2074 | * @return string |
||
2075 | */ |
||
2076 | public function firstBirthName() { |
||
2077 | return $this->mortalityQuery('name', 'ASC', 'BIRT'); |
||
2078 | } |
||
2079 | |||
2080 | /** |
||
2081 | * Find the earliest birth place. |
||
2082 | * |
||
2083 | * @return string |
||
2084 | */ |
||
2085 | public function firstBirthPlace() { |
||
2086 | return $this->mortalityQuery('place', 'ASC', 'BIRT'); |
||
2087 | } |
||
2088 | |||
2089 | /** |
||
2090 | * Find the latest birth. |
||
2091 | * |
||
2092 | * @return string |
||
2093 | */ |
||
2094 | public function lastBirth() { |
||
2095 | return $this->mortalityQuery('full', 'DESC', 'BIRT'); |
||
2096 | } |
||
2097 | |||
2098 | /** |
||
2099 | * Find the latest birth year. |
||
2100 | * |
||
2101 | * @return string |
||
2102 | */ |
||
2103 | public function lastBirthYear() { |
||
2104 | return $this->mortalityQuery('year', 'DESC', 'BIRT'); |
||
2105 | } |
||
2106 | |||
2107 | /** |
||
2108 | * Find the latest birth name. |
||
2109 | * |
||
2110 | * @return string |
||
2111 | */ |
||
2112 | public function lastBirthName() { |
||
2113 | return $this->mortalityQuery('name', 'DESC', 'BIRT'); |
||
2114 | } |
||
2115 | |||
2116 | /** |
||
2117 | * Find the latest birth place. |
||
2118 | * |
||
2119 | * @return string |
||
2120 | */ |
||
2121 | public function lastBirthPlace() { |
||
2122 | return $this->mortalityQuery('place', 'DESC', 'BIRT'); |
||
2123 | } |
||
2124 | |||
2125 | /** |
||
2126 | * General query on births. |
||
2127 | * |
||
2128 | * @param string[] $params |
||
2129 | * |
||
2130 | * @return string |
||
2131 | */ |
||
2132 | public function statsBirth($params = []) { |
||
2133 | return $this->statsBirthQuery(true, false, -1, -1, $params); |
||
2134 | } |
||
2135 | |||
2136 | /** |
||
2137 | * Find the earliest death. |
||
2138 | * |
||
2139 | * @return string |
||
2140 | */ |
||
2141 | public function firstDeath() { |
||
2142 | return $this->mortalityQuery('full', 'ASC', 'DEAT'); |
||
2143 | } |
||
2144 | |||
2145 | /** |
||
2146 | * Find the earliest death year. |
||
2147 | * |
||
2148 | * @return string |
||
2149 | */ |
||
2150 | public function firstDeathYear() { |
||
2151 | return $this->mortalityQuery('year', 'ASC', 'DEAT'); |
||
2152 | } |
||
2153 | |||
2154 | /** |
||
2155 | * Find the earliest death name. |
||
2156 | * |
||
2157 | * @return string |
||
2158 | */ |
||
2159 | public function firstDeathName() { |
||
2160 | return $this->mortalityQuery('name', 'ASC', 'DEAT'); |
||
2161 | } |
||
2162 | |||
2163 | /** |
||
2164 | * Find the earliest death place. |
||
2165 | * |
||
2166 | * @return string |
||
2167 | */ |
||
2168 | public function firstDeathPlace() { |
||
2169 | return $this->mortalityQuery('place', 'ASC', 'DEAT'); |
||
2170 | } |
||
2171 | |||
2172 | /** |
||
2173 | * Find the latest death. |
||
2174 | * |
||
2175 | * @return string |
||
2176 | */ |
||
2177 | public function lastDeath() { |
||
2178 | return $this->mortalityQuery('full', 'DESC', 'DEAT'); |
||
2179 | } |
||
2180 | |||
2181 | /** |
||
2182 | * Find the latest death year. |
||
2183 | * |
||
2184 | * @return string |
||
2185 | */ |
||
2186 | public function lastDeathYear() { |
||
2187 | return $this->mortalityQuery('year', 'DESC', 'DEAT'); |
||
2188 | } |
||
2189 | |||
2190 | /** |
||
2191 | * Find the latest death name. |
||
2192 | * |
||
2193 | * @return string |
||
2194 | */ |
||
2195 | public function lastDeathName() { |
||
2196 | return $this->mortalityQuery('name', 'DESC', 'DEAT'); |
||
2197 | } |
||
2198 | |||
2199 | /** |
||
2200 | * Find the place of the latest death. |
||
2201 | * |
||
2202 | * @return string |
||
2203 | */ |
||
2204 | public function lastDeathPlace() { |
||
2205 | return $this->mortalityQuery('place', 'DESC', 'DEAT'); |
||
2206 | } |
||
2207 | |||
2208 | /** |
||
2209 | * General query on deaths. |
||
2210 | * |
||
2211 | * @param string[] $params |
||
2212 | * |
||
2213 | * @return string |
||
2214 | */ |
||
2215 | public function statsDeath($params = []) { |
||
2216 | return $this->statsDeathQuery(true, false, -1, -1, $params); |
||
2217 | } |
||
2218 | |||
2219 | /** |
||
2220 | * Lifespan |
||
2221 | * |
||
2222 | * @param string $type |
||
2223 | * @param string $sex |
||
2224 | * |
||
2225 | * @return string |
||
2226 | */ |
||
2227 | private function longlifeQuery($type = 'full', $sex = 'F') { |
||
2228 | $sex_search = ' 1=1'; |
||
2229 | if ($sex == 'F') { |
||
2230 | $sex_search = " i_sex='F'"; |
||
2231 | } elseif ($sex == 'M') { |
||
2232 | $sex_search = " i_sex='M'"; |
||
2233 | } |
||
2234 | |||
2235 | $rows = $this->runSql( |
||
2236 | " SELECT SQL_CACHE" . |
||
2237 | " death.d_gid AS id," . |
||
2238 | " death.d_julianday2-birth.d_julianday1 AS age" . |
||
2239 | " FROM" . |
||
2240 | " `##dates` AS death," . |
||
2241 | " `##dates` AS birth," . |
||
2242 | " `##individuals` AS indi" . |
||
2243 | " WHERE" . |
||
2244 | " indi.i_id=birth.d_gid AND" . |
||
2245 | " birth.d_gid=death.d_gid AND" . |
||
2246 | " death.d_file={$this->tree->getTreeId()} AND" . |
||
2247 | " birth.d_file=death.d_file AND" . |
||
2248 | " birth.d_file=indi.i_file AND" . |
||
2249 | " birth.d_fact='BIRT' AND" . |
||
2250 | " death.d_fact='DEAT' AND" . |
||
2251 | " birth.d_julianday1<>0 AND" . |
||
2252 | " death.d_julianday1>birth.d_julianday2 AND" . |
||
2253 | $sex_search . |
||
2254 | " ORDER BY" . |
||
2255 | " age DESC LIMIT 1" |
||
2256 | ); |
||
2257 | if (!isset($rows[0])) { |
||
2258 | return ''; |
||
2259 | } |
||
2260 | $row = $rows[0]; |
||
2261 | $person = Individual::getInstance($row['id'], $this->tree); |
||
2262 | switch ($type) { |
||
2263 | default: |
||
2264 | case 'full': |
||
2265 | if ($person->canShowName()) { |
||
2266 | $result = $person->formatList(); |
||
2267 | } else { |
||
2268 | $result = I18N::translate('This information is private and cannot be shown.'); |
||
2269 | } |
||
2270 | break; |
||
2271 | case 'age': |
||
2272 | $result = I18N::number((int) ($row['age'] / 365.25)); |
||
2273 | break; |
||
2274 | case 'name': |
||
2275 | $result = '<a href="' . e($person->url()) . '">' . $person->getFullName() . '</a>'; |
||
2276 | break; |
||
2277 | } |
||
2278 | |||
2279 | return $result; |
||
2280 | } |
||
2281 | |||
2282 | /** |
||
2283 | * Find the oldest individuals. |
||
2284 | * |
||
2285 | * @param string $type |
||
2286 | * @param string $sex |
||
2287 | * @param string[] $params |
||
2288 | * |
||
2289 | * @return string |
||
2290 | */ |
||
2291 | private function topTenOldestQuery($type = 'list', $sex = 'BOTH', $params = []) { |
||
2292 | if ($sex === 'F') { |
||
2293 | $sex_search = " AND i_sex='F' "; |
||
2294 | } elseif ($sex === 'M') { |
||
2295 | $sex_search = " AND i_sex='M' "; |
||
2296 | } else { |
||
2297 | $sex_search = ''; |
||
2298 | } |
||
2299 | if (isset($params[0])) { |
||
2300 | $total = (int) $params[0]; |
||
2301 | } else { |
||
2302 | $total = 10; |
||
2303 | } |
||
2304 | $rows = $this->runSql( |
||
2305 | "SELECT SQL_CACHE " . |
||
2306 | " MAX(death.d_julianday2-birth.d_julianday1) AS age, " . |
||
2307 | " death.d_gid AS deathdate " . |
||
2308 | "FROM " . |
||
2309 | " `##dates` AS death, " . |
||
2310 | " `##dates` AS birth, " . |
||
2311 | " `##individuals` AS indi " . |
||
2312 | "WHERE " . |
||
2313 | " indi.i_id=birth.d_gid AND " . |
||
2314 | " birth.d_gid=death.d_gid AND " . |
||
2315 | " death.d_file={$this->tree->getTreeId()} AND " . |
||
2316 | " birth.d_file=death.d_file AND " . |
||
2317 | " birth.d_file=indi.i_file AND " . |
||
2318 | " birth.d_fact='BIRT' AND " . |
||
2319 | " death.d_fact='DEAT' AND " . |
||
2320 | " birth.d_julianday1<>0 AND " . |
||
2321 | " death.d_julianday1>birth.d_julianday2 " . |
||
2322 | $sex_search . |
||
2323 | "GROUP BY deathdate " . |
||
2324 | "ORDER BY age DESC " . |
||
2325 | "LIMIT " . $total |
||
2326 | ); |
||
2327 | if (!isset($rows[0])) { |
||
2328 | return ''; |
||
2329 | } |
||
2330 | $top10 = []; |
||
2331 | foreach ($rows as $row) { |
||
2332 | $person = Individual::getInstance($row['deathdate'], $this->tree); |
||
2333 | $age = $row['age']; |
||
2334 | if ((int) ($age / 365.25) > 0) { |
||
2335 | $age = (int) ($age / 365.25) . 'y'; |
||
2336 | } elseif ((int) ($age / 30.4375) > 0) { |
||
2337 | $age = (int) ($age / 30.4375) . 'm'; |
||
2338 | } else { |
||
2339 | $age = $age . 'd'; |
||
2340 | } |
||
2341 | $age = FunctionsDate::getAgeAtEvent($age); |
||
2342 | if ($person->canShow()) { |
||
2343 | if ($type == 'list') { |
||
2344 | $top10[] = '<li><a href="' . e($person->url()) . '">' . $person->getFullName() . '</a> (' . $age . ')' . '</li>'; |
||
2345 | } else { |
||
2346 | $top10[] = '<a href="' . e($person->url()) . '">' . $person->getFullName() . '</a> (' . $age . ')'; |
||
2347 | } |
||
2348 | } |
||
2349 | } |
||
2350 | if ($type == 'list') { |
||
2351 | $top10 = implode('', $top10); |
||
2352 | } else { |
||
2353 | $top10 = implode(' ', $top10); |
||
2354 | } |
||
2355 | if (I18N::direction() === 'rtl') { |
||
2356 | $top10 = str_replace(['[', ']', '(', ')', '+'], ['‏[', '‏]', '‏(', '‏)', '‏+'], $top10); |
||
2357 | } |
||
2358 | if ($type == 'list') { |
||
2359 | return '<ul>' . $top10 . '</ul>'; |
||
2360 | } |
||
2361 | |||
2362 | return $top10; |
||
2363 | } |
||
2364 | |||
2365 | /** |
||
2366 | * Find the oldest living individuals. |
||
2367 | * |
||
2368 | * @param string $type |
||
2369 | * @param string $sex |
||
2370 | * @param string[] $params |
||
2371 | * |
||
2372 | * @return string |
||
2373 | */ |
||
2374 | private function topTenOldestAliveQuery($type = 'list', $sex = 'BOTH', $params = []) { |
||
2375 | if (!Auth::isMember($this->tree)) { |
||
2376 | return I18N::translate('This information is private and cannot be shown.'); |
||
2377 | } |
||
2378 | if ($sex == 'F') { |
||
2379 | $sex_search = " AND i_sex='F'"; |
||
2380 | } elseif ($sex == 'M') { |
||
2381 | $sex_search = " AND i_sex='M'"; |
||
2382 | } else { |
||
2383 | $sex_search = ''; |
||
2384 | } |
||
2385 | if (isset($params[0])) { |
||
2386 | $total = (int) $params[0]; |
||
2387 | } else { |
||
2388 | $total = 10; |
||
2389 | } |
||
2390 | $rows = $this->runSql( |
||
2391 | "SELECT SQL_CACHE" . |
||
2392 | " birth.d_gid AS id," . |
||
2393 | " MIN(birth.d_julianday1) AS age" . |
||
2394 | " FROM" . |
||
2395 | " `##dates` AS birth," . |
||
2396 | " `##individuals` AS indi" . |
||
2397 | " WHERE" . |
||
2398 | " indi.i_id=birth.d_gid AND" . |
||
2399 | " indi.i_gedcom NOT REGEXP '\\n1 (" . WT_EVENTS_DEAT . ")' AND" . |
||
2400 | " birth.d_file={$this->tree->getTreeId()} AND" . |
||
2401 | " birth.d_fact='BIRT' AND" . |
||
2402 | " birth.d_file=indi.i_file AND" . |
||
2403 | " birth.d_julianday1<>0" . |
||
2404 | $sex_search . |
||
2405 | " GROUP BY id" . |
||
2406 | " ORDER BY age" . |
||
2407 | " ASC LIMIT " . $total |
||
2408 | ); |
||
2409 | $top10 = []; |
||
2410 | foreach ($rows as $row) { |
||
2411 | $person = Individual::getInstance($row['id'], $this->tree); |
||
2412 | $age = (WT_CLIENT_JD - $row['age']); |
||
2413 | if ((int) ($age / 365.25) > 0) { |
||
2414 | $age = (int) ($age / 365.25) . 'y'; |
||
2415 | } elseif ((int) ($age / 30.4375) > 0) { |
||
2416 | $age = (int) ($age / 30.4375) . 'm'; |
||
2417 | } else { |
||
2418 | $age = $age . 'd'; |
||
2419 | } |
||
2420 | $age = FunctionsDate::getAgeAtEvent($age); |
||
2421 | if ($type === 'list') { |
||
2422 | $top10[] = '<li><a href="' . e($person->url()) . '">' . $person->getFullName() . '</a> (' . $age . ')' . '</li>'; |
||
2423 | } else { |
||
2424 | $top10[] = '<a href="' . e($person->url()) . '">' . $person->getFullName() . '</a> (' . $age . ')'; |
||
2425 | } |
||
2426 | } |
||
2427 | if ($type === 'list') { |
||
2428 | $top10 = implode('', $top10); |
||
2429 | } else { |
||
2430 | $top10 = implode('; ', $top10); |
||
2431 | } |
||
2432 | if (I18N::direction() === 'rtl') { |
||
2433 | $top10 = str_replace(['[', ']', '(', ')', '+'], ['‏[', '‏]', '‏(', '‏)', '‏+'], $top10); |
||
2434 | } |
||
2435 | if ($type === 'list') { |
||
2436 | return '<ul>' . $top10 . '</ul>'; |
||
2437 | } |
||
2438 | |||
2439 | return $top10; |
||
2440 | } |
||
2441 | |||
2442 | /** |
||
2443 | * Find the average lifespan. |
||
2444 | * |
||
2445 | * @param string $sex |
||
2446 | * @param bool $show_years |
||
2447 | * |
||
2448 | * @return string |
||
2449 | */ |
||
2450 | private function averageLifespanQuery($sex = 'BOTH', $show_years = false) { |
||
2451 | if ($sex === 'F') { |
||
2452 | $sex_search = " AND i_sex='F' "; |
||
2453 | } elseif ($sex === 'M') { |
||
2454 | $sex_search = " AND i_sex='M' "; |
||
2455 | } else { |
||
2456 | $sex_search = ''; |
||
2457 | } |
||
2458 | $rows = $this->runSql( |
||
2459 | "SELECT SQL_CACHE " . |
||
2460 | " AVG(death.d_julianday2-birth.d_julianday1) AS age " . |
||
2461 | "FROM " . |
||
2462 | " `##dates` AS death, " . |
||
2463 | " `##dates` AS birth, " . |
||
2464 | " `##individuals` AS indi " . |
||
2465 | "WHERE " . |
||
2466 | " indi.i_id=birth.d_gid AND " . |
||
2467 | " birth.d_gid=death.d_gid AND " . |
||
2468 | " death.d_file=" . $this->tree->getTreeId() . " AND " . |
||
2469 | " birth.d_file=death.d_file AND " . |
||
2470 | " birth.d_file=indi.i_file AND " . |
||
2471 | " birth.d_fact='BIRT' AND " . |
||
2472 | " death.d_fact='DEAT' AND " . |
||
2473 | " birth.d_julianday1<>0 AND " . |
||
2474 | " death.d_julianday1>birth.d_julianday2 " . |
||
2475 | $sex_search |
||
2476 | ); |
||
2477 | if (!isset($rows[0])) { |
||
2478 | return ''; |
||
2479 | } |
||
2480 | $row = $rows[0]; |
||
2481 | $age = $row['age']; |
||
2482 | if ($show_years) { |
||
2483 | if ((int) ($age / 365.25) > 0) { |
||
2484 | $age = (int) ($age / 365.25) . 'y'; |
||
2485 | } elseif ((int) ($age / 30.4375) > 0) { |
||
2486 | $age = (int) ($age / 30.4375) . 'm'; |
||
2487 | } elseif (!empty($age)) { |
||
2488 | $age = $age . 'd'; |
||
2489 | } |
||
2490 | |||
2491 | return FunctionsDate::getAgeAtEvent($age); |
||
2492 | } else { |
||
2493 | return I18N::number($age / 365.25); |
||
2494 | } |
||
2495 | } |
||
2496 | |||
2497 | /** |
||
2498 | * General query on ages. |
||
2499 | * |
||
2500 | * @param bool $simple |
||
2501 | * @param string $related |
||
2502 | * @param string $sex |
||
2503 | * @param int $year1 |
||
2504 | * @param int $year2 |
||
2505 | * @param string[] $params |
||
2506 | * |
||
2507 | * @return array|string |
||
2508 | */ |
||
2509 | public function statsAgeQuery($simple = true, $related = 'BIRT', $sex = 'BOTH', $year1 = -1, $year2 = -1, $params = []) { |
||
2510 | if ($simple) { |
||
2511 | if (isset($params[0]) && $params[0] != '') { |
||
2512 | $size = strtolower($params[0]); |
||
2513 | } else { |
||
2514 | $size = '230x250'; |
||
2515 | } |
||
2516 | $sizes = explode('x', $size); |
||
2517 | $rows = $this->runSql( |
||
2518 | "SELECT SQL_CACHE" . |
||
2519 | " ROUND(AVG(death.d_julianday2-birth.d_julianday1)/365.25,1) AS age," . |
||
2520 | " FLOOR(death.d_year/100+1) AS century," . |
||
2521 | " i_sex AS sex" . |
||
2522 | " FROM" . |
||
2523 | " `##dates` AS death," . |
||
2524 | " `##dates` AS birth," . |
||
2525 | " `##individuals` AS indi" . |
||
2526 | " WHERE" . |
||
2527 | " indi.i_id=birth.d_gid AND" . |
||
2528 | " birth.d_gid=death.d_gid AND" . |
||
2529 | " death.d_file={$this->tree->getTreeId()} AND" . |
||
2530 | " birth.d_file=death.d_file AND" . |
||
2531 | " birth.d_file=indi.i_file AND" . |
||
2532 | " birth.d_fact='BIRT' AND" . |
||
2533 | " death.d_fact='DEAT' AND" . |
||
2534 | " birth.d_julianday1<>0 AND" . |
||
2535 | " birth.d_type IN ('@#DGREGORIAN@', '@#DJULIAN@') AND" . |
||
2536 | " death.d_type IN ('@#DGREGORIAN@', '@#DJULIAN@') AND" . |
||
2537 | " death.d_julianday1>birth.d_julianday2" . |
||
2538 | " GROUP BY century, sex ORDER BY century, sex"); |
||
2539 | if (empty($rows)) { |
||
2540 | return ''; |
||
2541 | } |
||
2542 | $chxl = '0:|'; |
||
2543 | $countsm = ''; |
||
2544 | $countsf = ''; |
||
2545 | $countsa = ''; |
||
2546 | $out = []; |
||
2547 | foreach ($rows as $values) { |
||
2548 | $out[$values['century']][$values['sex']] = $values['age']; |
||
2549 | } |
||
2550 | foreach ($out as $century => $values) { |
||
2551 | if ($sizes[0] < 980) { |
||
2552 | $sizes[0] += 50; |
||
2553 | } |
||
2554 | $chxl .= $this->centuryName($century) . '|'; |
||
2555 | $average = 0; |
||
2556 | if (isset($values['F'])) { |
||
2557 | $countsf .= $values['F'] . ','; |
||
2558 | $average = $values['F']; |
||
2559 | } else { |
||
2560 | $countsf .= '0,'; |
||
2561 | } |
||
2562 | if (isset($values['M'])) { |
||
2563 | $countsm .= $values['M'] . ','; |
||
2564 | if ($average == 0) { |
||
2565 | $countsa .= $values['M'] . ','; |
||
2566 | } else { |
||
2567 | $countsa .= (($values['M'] + $average) / 2) . ','; |
||
2568 | } |
||
2569 | } else { |
||
2570 | $countsm .= '0,'; |
||
2571 | if ($average == 0) { |
||
2572 | $countsa .= '0,'; |
||
2573 | } else { |
||
2574 | $countsa .= $values['F'] . ','; |
||
2575 | } |
||
2576 | } |
||
2577 | } |
||
2578 | $countsm = substr($countsm, 0, -1); |
||
2579 | $countsf = substr($countsf, 0, -1); |
||
2580 | $countsa = substr($countsa, 0, -1); |
||
2581 | $chd = 't2:' . $countsm . '|' . $countsf . '|' . $countsa; |
||
2582 | $decades = ''; |
||
2583 | for ($i = 0; $i <= 100; $i += 10) { |
||
2584 | $decades .= '|' . I18N::number($i); |
||
2585 | } |
||
2586 | $chxl .= '1:||' . I18N::translate('century') . '|2:' . $decades . '|3:||' . I18N::translate('Age') . '|'; |
||
2587 | $title = I18N::translate('Average age related to death century'); |
||
2588 | if (count($rows) > 6 || mb_strlen($title) < 30) { |
||
2589 | $chtt = $title; |
||
2590 | } else { |
||
2591 | $offset = 0; |
||
2592 | $counter = []; |
||
2593 | while ($offset = strpos($title, ' ', $offset + 1)) { |
||
2594 | $counter[] = $offset; |
||
2595 | } |
||
2596 | $half = (int) (count($counter) / 2); |
||
2597 | $chtt = substr_replace($title, '|', $counter[$half], 1); |
||
2598 | } |
||
2599 | |||
2600 | return '<img src="' . "https://chart.googleapis.com/chart?cht=bvg&chs={$sizes[0]}x{$sizes[1]}&chm=D,FF0000,2,0,3,1|N*f1*,000000,0,-1,11,1|N*f1*,000000,1,-1,11,1&chf=bg,s,ffffff00|c,s,ffffff00&chtt=" . rawurlencode($chtt) . "&chd={$chd}&chco=0000FF,FFA0CB,FF0000&chbh=20,3&chxt=x,x,y,y&chxl=" . rawurlencode($chxl) . '&chdl=' . rawurlencode(I18N::translate('Males') . '|' . I18N::translate('Females') . '|' . I18N::translate('Average age at death')) . "\" width=\"{$sizes[0]}\" height=\"{$sizes[1]}\" alt=\"" . I18N::translate('Average age related to death century') . '" title="' . I18N::translate('Average age related to death century') . '" />'; |
||
2601 | } else { |
||
2602 | $sex_search = ''; |
||
2603 | $years = ''; |
||
2604 | if ($sex == 'F') { |
||
2605 | $sex_search = " AND i_sex='F'"; |
||
2606 | } elseif ($sex == 'M') { |
||
2607 | $sex_search = " AND i_sex='M'"; |
||
2608 | } |
||
2609 | if ($year1 >= 0 && $year2 >= 0) { |
||
2610 | if ($related == 'BIRT') { |
||
2611 | $years = " AND birth.d_year BETWEEN '{$year1}' AND '{$year2}'"; |
||
2612 | } elseif ($related == 'DEAT') { |
||
2613 | $years = " AND death.d_year BETWEEN '{$year1}' AND '{$year2}'"; |
||
2614 | } |
||
2615 | } |
||
2616 | $rows = $this->runSql( |
||
2617 | "SELECT SQL_CACHE" . |
||
2618 | " death.d_julianday2-birth.d_julianday1 AS age" . |
||
2619 | " FROM" . |
||
2620 | " `##dates` AS death," . |
||
2621 | " `##dates` AS birth," . |
||
2622 | " `##individuals` AS indi" . |
||
2623 | " WHERE" . |
||
2624 | " indi.i_id=birth.d_gid AND" . |
||
2625 | " birth.d_gid=death.d_gid AND" . |
||
2626 | " death.d_file={$this->tree->getTreeId()} AND" . |
||
2627 | " birth.d_file=death.d_file AND" . |
||
2628 | " birth.d_file=indi.i_file AND" . |
||
2629 | " birth.d_fact='BIRT' AND" . |
||
2630 | " death.d_fact='DEAT' AND" . |
||
2631 | " birth.d_julianday1 <> 0 AND" . |
||
2632 | " birth.d_type IN ('@#DGREGORIAN@', '@#DJULIAN@') AND" . |
||
2633 | " death.d_type IN ('@#DGREGORIAN@', '@#DJULIAN@') AND" . |
||
2634 | " death.d_julianday1>birth.d_julianday2" . |
||
2635 | $years . |
||
2636 | $sex_search . |
||
2637 | " ORDER BY age DESC"); |
||
2638 | |||
2639 | return $rows; |
||
2640 | } |
||
2641 | } |
||
2642 | |||
2643 | /** |
||
2644 | * General query on ages. |
||
2645 | * |
||
2646 | * @param string[] $params |
||
2647 | * |
||
2648 | * @return string |
||
2649 | */ |
||
2650 | public function statsAge($params = []) { |
||
2651 | return $this->statsAgeQuery(true, 'BIRT', 'BOTH', -1, -1, $params); |
||
2652 | } |
||
2653 | |||
2654 | /** |
||
2655 | * Find the lognest lived individual. |
||
2656 | * |
||
2657 | * @return string |
||
2658 | */ |
||
2659 | public function longestLife() { |
||
2660 | return $this->longlifeQuery('full', 'BOTH'); |
||
2661 | } |
||
2662 | |||
2663 | /** |
||
2664 | * Find the age of the longest lived individual. |
||
2665 | * |
||
2666 | * @return string |
||
2667 | */ |
||
2668 | public function longestLifeAge() { |
||
2669 | return $this->longlifeQuery('age', 'BOTH'); |
||
2670 | } |
||
2671 | |||
2672 | /** |
||
2673 | * Find the name of the longest lived individual. |
||
2674 | * |
||
2675 | * @return string |
||
2676 | */ |
||
2677 | public function longestLifeName() { |
||
2678 | return $this->longlifeQuery('name', 'BOTH'); |
||
2679 | } |
||
2680 | |||
2681 | /** |
||
2682 | * Find the oldest individuals. |
||
2683 | * |
||
2684 | * @param string[] $params |
||
2685 | * |
||
2686 | * @return string |
||
2687 | */ |
||
2688 | public function topTenOldest($params = []) { |
||
2689 | return $this->topTenOldestQuery('nolist', 'BOTH', $params); |
||
2690 | } |
||
2691 | |||
2692 | /** |
||
2693 | * Find the oldest living individuals. |
||
2694 | * |
||
2695 | * @param string[] $params |
||
2696 | * |
||
2697 | * @return string |
||
2698 | */ |
||
2699 | public function topTenOldestList($params = []) { |
||
2700 | return $this->topTenOldestQuery('list', 'BOTH', $params); |
||
2701 | } |
||
2702 | |||
2703 | /** |
||
2704 | * Find the oldest living individuals. |
||
2705 | * |
||
2706 | * @param string[] $params |
||
2707 | * |
||
2708 | * @return string |
||
2709 | */ |
||
2710 | public function topTenOldestAlive($params = []) { |
||
2711 | return $this->topTenOldestAliveQuery('nolist', 'BOTH', $params); |
||
2712 | } |
||
2713 | |||
2714 | /** |
||
2715 | * Find the oldest living individuals. |
||
2716 | * |
||
2717 | * @param string[] $params |
||
2718 | * |
||
2719 | * @return string |
||
2720 | */ |
||
2721 | public function topTenOldestListAlive($params = []) { |
||
2722 | return $this->topTenOldestAliveQuery('list', 'BOTH', $params); |
||
2723 | } |
||
2724 | |||
2725 | /** |
||
2726 | * Find the average lifespan. |
||
2727 | * |
||
2728 | * @param bool $show_years |
||
2729 | * |
||
2730 | * @return string |
||
2731 | */ |
||
2732 | public function averageLifespan($show_years = false) { |
||
2733 | return $this->averageLifespanQuery('BOTH', $show_years); |
||
2734 | } |
||
2735 | |||
2736 | /** |
||
2737 | * Find the longest lived female. |
||
2738 | * |
||
2739 | * @return string |
||
2740 | */ |
||
2741 | public function longestLifeFemale() { |
||
2742 | return $this->longlifeQuery('full', 'F'); |
||
2743 | } |
||
2744 | |||
2745 | /** |
||
2746 | * Find the age of the longest lived female. |
||
2747 | * |
||
2748 | * @return string |
||
2749 | */ |
||
2750 | public function longestLifeFemaleAge() { |
||
2751 | return $this->longlifeQuery('age', 'F'); |
||
2752 | } |
||
2753 | |||
2754 | /** |
||
2755 | * Find the name of the longest lived female. |
||
2756 | * |
||
2757 | * @return string |
||
2758 | */ |
||
2759 | public function longestLifeFemaleName() { |
||
2760 | return $this->longlifeQuery('name', 'F'); |
||
2761 | } |
||
2762 | |||
2763 | /** |
||
2764 | * Find the oldest females. |
||
2765 | * |
||
2766 | * @param string[] $params |
||
2767 | * |
||
2768 | * @return string |
||
2769 | */ |
||
2770 | public function topTenOldestFemale($params = []) { |
||
2771 | return $this->topTenOldestQuery('nolist', 'F', $params); |
||
2772 | } |
||
2773 | |||
2774 | /** |
||
2775 | * Find the oldest living females. |
||
2776 | * |
||
2777 | * @param string[] $params |
||
2778 | * |
||
2779 | * @return string |
||
2780 | */ |
||
2781 | public function topTenOldestFemaleList($params = []) { |
||
2782 | return $this->topTenOldestQuery('list', 'F', $params); |
||
2783 | } |
||
2784 | |||
2785 | /** |
||
2786 | * Find the oldest living females. |
||
2787 | * |
||
2788 | * @param string[] $params |
||
2789 | * |
||
2790 | * @return string |
||
2791 | */ |
||
2792 | public function topTenOldestFemaleAlive($params = []) { |
||
2793 | return $this->topTenOldestAliveQuery('nolist', 'F', $params); |
||
2794 | } |
||
2795 | |||
2796 | /** |
||
2797 | * Find the oldest living females. |
||
2798 | * |
||
2799 | * @param string[] $params |
||
2800 | * |
||
2801 | * @return string |
||
2802 | */ |
||
2803 | public function topTenOldestFemaleListAlive($params = []) { |
||
2804 | return $this->topTenOldestAliveQuery('list', 'F', $params); |
||
2805 | } |
||
2806 | |||
2807 | /** |
||
2808 | * Find the average lifespan of females. |
||
2809 | * |
||
2810 | * @param bool $show_years |
||
2811 | * |
||
2812 | * @return string |
||
2813 | */ |
||
2814 | public function averageLifespanFemale($show_years = false) { |
||
2815 | return $this->averageLifespanQuery('F', $show_years); |
||
2816 | } |
||
2817 | |||
2818 | /** |
||
2819 | * Find the longest lived male. |
||
2820 | * |
||
2821 | * @return string |
||
2822 | */ |
||
2823 | public function longestLifeMale() { |
||
2824 | return $this->longlifeQuery('full', 'M'); |
||
2825 | } |
||
2826 | |||
2827 | /** |
||
2828 | * Find the age of the longest lived male. |
||
2829 | * |
||
2830 | * @return string |
||
2831 | */ |
||
2832 | public function longestLifeMaleAge() { |
||
2833 | return $this->longlifeQuery('age', 'M'); |
||
2834 | } |
||
2835 | |||
2836 | /** |
||
2837 | * Find the name of the longest lived male. |
||
2838 | * |
||
2839 | * @return string |
||
2840 | */ |
||
2841 | public function longestLifeMaleName() { |
||
2842 | return $this->longlifeQuery('name', 'M'); |
||
2843 | } |
||
2844 | |||
2845 | /** |
||
2846 | * Find the longest lived males. |
||
2847 | * |
||
2848 | * @param string[] $params |
||
2849 | * |
||
2850 | * @return string |
||
2851 | */ |
||
2852 | public function topTenOldestMale($params = []) { |
||
2853 | return $this->topTenOldestQuery('nolist', 'M', $params); |
||
2854 | } |
||
2855 | |||
2856 | /** |
||
2857 | * Find the longest lived males. |
||
2858 | * |
||
2859 | * @param string[] $params |
||
2860 | * |
||
2861 | * @return string |
||
2862 | */ |
||
2863 | public function topTenOldestMaleList($params = []) { |
||
2864 | return $this->topTenOldestQuery('list', 'M', $params); |
||
2865 | } |
||
2866 | |||
2867 | /** |
||
2868 | * Find the longest lived living males. |
||
2869 | * |
||
2870 | * @param string[] $params |
||
2871 | * |
||
2872 | * @return string |
||
2873 | */ |
||
2874 | public function topTenOldestMaleAlive($params = []) { |
||
2875 | return $this->topTenOldestAliveQuery('nolist', 'M', $params); |
||
2876 | } |
||
2877 | |||
2878 | /** |
||
2879 | * Find the longest lived living males. |
||
2880 | * |
||
2881 | * @param string[] $params |
||
2882 | * |
||
2883 | * @return string |
||
2884 | */ |
||
2885 | public function topTenOldestMaleListAlive($params = []) { |
||
2886 | return $this->topTenOldestAliveQuery('list', 'M', $params); |
||
2887 | } |
||
2888 | |||
2889 | /** |
||
2890 | * Find the average male lifespan. |
||
2891 | * |
||
2892 | * @param bool $show_years |
||
2893 | * |
||
2894 | * @return string |
||
2895 | */ |
||
2896 | public function averageLifespanMale($show_years = false) { |
||
2897 | return $this->averageLifespanQuery('M', $show_years); |
||
2898 | } |
||
2899 | |||
2900 | /** |
||
2901 | * Events |
||
2902 | * |
||
2903 | * @param string $type |
||
2904 | * @param string $direction |
||
2905 | * @param string $facts |
||
2906 | * |
||
2907 | * @return string |
||
2908 | */ |
||
2909 | private function eventQuery($type, $direction, $facts) { |
||
2910 | $eventTypes = [ |
||
2911 | 'BIRT' => I18N::translate('birth'), |
||
2912 | 'DEAT' => I18N::translate('death'), |
||
2913 | 'MARR' => I18N::translate('marriage'), |
||
2914 | 'ADOP' => I18N::translate('adoption'), |
||
2915 | 'BURI' => I18N::translate('burial'), |
||
2916 | 'CENS' => I18N::translate('census added'), |
||
2917 | ]; |
||
2918 | |||
2919 | $fact_query = "IN ('" . str_replace('|', "','", $facts) . "')"; |
||
2920 | |||
2921 | if ($direction != 'ASC') { |
||
2922 | $direction = 'DESC'; |
||
2923 | } |
||
2924 | $rows = $this->runSql('' |
||
2925 | . ' SELECT SQL_CACHE' |
||
2926 | . ' d_gid AS id,' |
||
2927 | . ' d_year AS year,' |
||
2928 | . ' d_fact AS fact,' |
||
2929 | . ' d_type AS type' |
||
2930 | . ' FROM' |
||
2931 | . " `##dates`" |
||
2932 | . ' WHERE' |
||
2933 | . " d_file={$this->tree->getTreeId()} AND" |
||
2934 | . " d_gid<>'HEAD' AND" |
||
2935 | . " d_fact {$fact_query} AND" |
||
2936 | . ' d_julianday1<>0' |
||
2937 | . ' ORDER BY' |
||
2938 | . " d_julianday1 {$direction}, d_type LIMIT 1" |
||
2939 | ); |
||
2940 | if (!isset($rows[0])) { |
||
2941 | return ''; |
||
2942 | } |
||
2943 | $row = $rows[0]; |
||
2944 | $record = GedcomRecord::getInstance($row['id'], $this->tree); |
||
2945 | switch ($type) { |
||
2946 | default: |
||
2947 | case 'full': |
||
2948 | if ($record->canShow()) { |
||
2949 | $result = $record->formatList(); |
||
2950 | } else { |
||
2951 | $result = I18N::translate('This information is private and cannot be shown.'); |
||
2952 | } |
||
2953 | break; |
||
2954 | case 'year': |
||
2955 | $date = new Date($row['type'] . ' ' . $row['year']); |
||
2956 | $result = $date->display(); |
||
2957 | break; |
||
2958 | case 'type': |
||
2959 | if (isset($eventTypes[$row['fact']])) { |
||
2960 | $result = $eventTypes[$row['fact']]; |
||
2961 | } else { |
||
2962 | $result = GedcomTag::getLabel($row['fact']); |
||
2963 | } |
||
2964 | break; |
||
2965 | case 'name': |
||
2966 | $result = '<a href="' . e($record->url()) . '">' . $record->getFullName() . '</a>'; |
||
2967 | break; |
||
2968 | case 'place': |
||
2969 | $fact = $record->getFirstFact($row['fact']); |
||
2970 | if ($fact) { |
||
2971 | $result = FunctionsPrint::formatFactPlace($fact, true, true, true); |
||
2972 | } else { |
||
2973 | $result = I18N::translate('Private'); |
||
2974 | } |
||
2975 | break; |
||
2976 | } |
||
2977 | |||
2978 | return $result; |
||
2979 | } |
||
2980 | |||
2981 | /** |
||
2982 | * Find the earliest event. |
||
2983 | * |
||
2984 | * @return string |
||
2985 | */ |
||
2986 | public function firstEvent() { |
||
2987 | return $this->eventQuery('full', 'ASC', WT_EVENTS_BIRT . '|' . WT_EVENTS_MARR . '|' . WT_EVENTS_DIV . '|' . WT_EVENTS_DEAT); |
||
2988 | } |
||
2989 | |||
2990 | /** |
||
2991 | * Find the year of the earliest event. |
||
2992 | * |
||
2993 | * @return string |
||
2994 | */ |
||
2995 | public function firstEventYear() { |
||
2996 | return $this->eventQuery('year', 'ASC', WT_EVENTS_BIRT . '|' . WT_EVENTS_MARR . '|' . WT_EVENTS_DIV . '|' . WT_EVENTS_DEAT); |
||
2997 | } |
||
2998 | |||
2999 | /** |
||
3000 | * Find the type of the earliest event. |
||
3001 | * |
||
3002 | * @return string |
||
3003 | */ |
||
3004 | public function firstEventType() { |
||
3005 | return $this->eventQuery('type', 'ASC', WT_EVENTS_BIRT . '|' . WT_EVENTS_MARR . '|' . WT_EVENTS_DIV . '|' . WT_EVENTS_DEAT); |
||
3006 | } |
||
3007 | |||
3008 | /** |
||
3009 | * Find the name of the individual with the earliest event. |
||
3010 | * |
||
3011 | * @return string |
||
3012 | */ |
||
3013 | public function firstEventName() { |
||
3014 | return $this->eventQuery('name', 'ASC', WT_EVENTS_BIRT . '|' . WT_EVENTS_MARR . '|' . WT_EVENTS_DIV . '|' . WT_EVENTS_DEAT); |
||
3015 | } |
||
3016 | |||
3017 | /** |
||
3018 | * Find the location of the earliest event. |
||
3019 | * |
||
3020 | * @return string |
||
3021 | */ |
||
3022 | public function firstEventPlace() { |
||
3023 | return $this->eventQuery('place', 'ASC', WT_EVENTS_BIRT . '|' . WT_EVENTS_MARR . '|' . WT_EVENTS_DIV . '|' . WT_EVENTS_DEAT); |
||
3024 | } |
||
3025 | |||
3026 | /** |
||
3027 | * Find the latest event. |
||
3028 | * |
||
3029 | * @return string |
||
3030 | */ |
||
3031 | public function lastEvent() { |
||
3032 | return $this->eventQuery('full', 'DESC', WT_EVENTS_BIRT . '|' . WT_EVENTS_MARR . '|' . WT_EVENTS_DIV . '|' . WT_EVENTS_DEAT); |
||
3033 | } |
||
3034 | |||
3035 | /** |
||
3036 | * Find the year of the latest event. |
||
3037 | * |
||
3038 | * @return string |
||
3039 | */ |
||
3040 | public function lastEventYear() { |
||
3041 | return $this->eventQuery('year', 'DESC', WT_EVENTS_BIRT . '|' . WT_EVENTS_MARR . '|' . WT_EVENTS_DIV . '|' . WT_EVENTS_DEAT); |
||
3042 | } |
||
3043 | |||
3044 | /** |
||
3045 | * Find the type of the latest event. |
||
3046 | * |
||
3047 | * @return string |
||
3048 | */ |
||
3049 | public function lastEventType() { |
||
3050 | return $this->eventQuery('type', 'DESC', WT_EVENTS_BIRT . '|' . WT_EVENTS_MARR . '|' . WT_EVENTS_DIV . '|' . WT_EVENTS_DEAT); |
||
3051 | } |
||
3052 | |||
3053 | /** |
||
3054 | * Find the name of the individual with the latest event. |
||
3055 | * |
||
3056 | * @return string |
||
3057 | */ |
||
3058 | public function lastEventName() { |
||
3059 | return $this->eventQuery('name', 'DESC', WT_EVENTS_BIRT . '|' . WT_EVENTS_MARR . '|' . WT_EVENTS_DIV . '|' . WT_EVENTS_DEAT); |
||
3060 | } |
||
3061 | |||
3062 | /** |
||
3063 | * FInd the location of the latest event. |
||
3064 | * |
||
3065 | * @return string |
||
3066 | */ |
||
3067 | public function lastEventPlace() { |
||
3068 | return $this->eventQuery('place', 'DESC', WT_EVENTS_BIRT . '|' . WT_EVENTS_MARR . '|' . WT_EVENTS_DIV . '|' . WT_EVENTS_DEAT); |
||
3069 | } |
||
3070 | |||
3071 | /** |
||
3072 | * Query the database for marriage tags. |
||
3073 | * |
||
3074 | * @param string $type |
||
3075 | * @param string $age_dir |
||
3076 | * @param string $sex |
||
3077 | * @param bool $show_years |
||
3078 | * |
||
3079 | * @return string |
||
3080 | */ |
||
3081 | private function marriageQuery($type = 'full', $age_dir = 'ASC', $sex = 'F', $show_years = false) { |
||
3082 | if ($sex == 'F') { |
||
3083 | $sex_field = 'f_wife'; |
||
3084 | } else { |
||
3085 | $sex_field = 'f_husb'; |
||
3086 | } |
||
3087 | if ($age_dir != 'ASC') { |
||
3088 | $age_dir = 'DESC'; |
||
3089 | } |
||
3090 | $rows = $this->runSql( |
||
3091 | " SELECT SQL_CACHE fam.f_id AS famid, fam.{$sex_field}, married.d_julianday2-birth.d_julianday1 AS age, indi.i_id AS i_id" . |
||
3092 | " FROM `##families` AS fam" . |
||
3093 | " LEFT JOIN `##dates` AS birth ON birth.d_file = {$this->tree->getTreeId()}" . |
||
3094 | " LEFT JOIN `##dates` AS married ON married.d_file = {$this->tree->getTreeId()}" . |
||
3095 | " LEFT JOIN `##individuals` AS indi ON indi.i_file = {$this->tree->getTreeId()}" . |
||
3096 | " WHERE" . |
||
3097 | " birth.d_gid = indi.i_id AND" . |
||
3098 | " married.d_gid = fam.f_id AND" . |
||
3099 | " indi.i_id = fam.{$sex_field} AND" . |
||
3100 | " fam.f_file = {$this->tree->getTreeId()} AND" . |
||
3101 | " birth.d_fact = 'BIRT' AND" . |
||
3102 | " married.d_fact = 'MARR' AND" . |
||
3103 | " birth.d_julianday1 <> 0 AND" . |
||
3104 | " married.d_julianday2 > birth.d_julianday1 AND" . |
||
3105 | " i_sex='{$sex}'" . |
||
3106 | " ORDER BY" . |
||
3107 | " married.d_julianday2-birth.d_julianday1 {$age_dir} LIMIT 1" |
||
3108 | ); |
||
3109 | if (!isset($rows[0])) { |
||
3110 | return ''; |
||
3111 | } |
||
3112 | $row = $rows[0]; |
||
3113 | if (isset($row['famid'])) { |
||
3114 | $family = Family::getInstance($row['famid'], $this->tree); |
||
3115 | } |
||
3116 | if (isset($row['i_id'])) { |
||
3117 | $person = Individual::getInstance($row['i_id'], $this->tree); |
||
3118 | } |
||
3119 | switch ($type) { |
||
3120 | default: |
||
3121 | case 'full': |
||
3122 | if ($family->canShow()) { |
||
3123 | $result = $family->formatList(); |
||
3124 | } else { |
||
3125 | $result = I18N::translate('This information is private and cannot be shown.'); |
||
3126 | } |
||
3127 | break; |
||
3128 | case 'name': |
||
3129 | $result = '<a href="' . e($family->url()) . '">' . $person->getFullName() . '</a>'; |
||
3130 | break; |
||
3131 | case 'age': |
||
3132 | $age = $row['age']; |
||
3133 | if ($show_years) { |
||
3134 | if ((int) ($age / 365.25) > 0) { |
||
3135 | $age = (int) ($age / 365.25) . 'y'; |
||
3136 | } elseif ((int) ($age / 30.4375) > 0) { |
||
3137 | $age = (int) ($age / 30.4375) . 'm'; |
||
3138 | } else { |
||
3139 | $age = $age . 'd'; |
||
3140 | } |
||
3141 | $result = FunctionsDate::getAgeAtEvent($age); |
||
3142 | } else { |
||
3143 | $result = I18N::number((int) ($age / 365.25)); |
||
3144 | } |
||
3145 | break; |
||
3146 | } |
||
3147 | |||
3148 | return $result; |
||
3149 | } |
||
3150 | |||
3151 | /** |
||
3152 | * General query on age at marriage. |
||
3153 | * |
||
3154 | * @param string $type |
||
3155 | * @param string $age_dir |
||
3156 | * @param string[] $params |
||
3157 | * |
||
3158 | * @return string |
||
3159 | */ |
||
3160 | private function ageOfMarriageQuery($type = 'list', $age_dir = 'ASC', $params = []) { |
||
3161 | if (isset($params[0])) { |
||
3162 | $total = (int) $params[0]; |
||
3163 | } else { |
||
3164 | $total = 10; |
||
3165 | } |
||
3166 | if ($age_dir != 'ASC') { |
||
3167 | $age_dir = 'DESC'; |
||
3168 | } |
||
3169 | $hrows = $this->runSql( |
||
3170 | " SELECT SQL_CACHE DISTINCT fam.f_id AS family, MIN(husbdeath.d_julianday2-married.d_julianday1) AS age" . |
||
3171 | " FROM `##families` AS fam" . |
||
3172 | " LEFT JOIN `##dates` AS married ON married.d_file = {$this->tree->getTreeId()}" . |
||
3173 | " LEFT JOIN `##dates` AS husbdeath ON husbdeath.d_file = {$this->tree->getTreeId()}" . |
||
3174 | " WHERE" . |
||
3175 | " fam.f_file = {$this->tree->getTreeId()} AND" . |
||
3176 | " husbdeath.d_gid = fam.f_husb AND" . |
||
3177 | " husbdeath.d_fact = 'DEAT' AND" . |
||
3178 | " married.d_gid = fam.f_id AND" . |
||
3179 | " married.d_fact = 'MARR' AND" . |
||
3180 | " married.d_julianday1 < husbdeath.d_julianday2 AND" . |
||
3181 | " married.d_julianday1 <> 0" . |
||
3182 | " GROUP BY family" . |
||
3183 | " ORDER BY age {$age_dir}"); |
||
3184 | $wrows = $this->runSql( |
||
3185 | " SELECT SQL_CACHE DISTINCT fam.f_id AS family, MIN(wifedeath.d_julianday2-married.d_julianday1) AS age" . |
||
3186 | " FROM `##families` AS fam" . |
||
3187 | " LEFT JOIN `##dates` AS married ON married.d_file = {$this->tree->getTreeId()}" . |
||
3188 | " LEFT JOIN `##dates` AS wifedeath ON wifedeath.d_file = {$this->tree->getTreeId()}" . |
||
3189 | " WHERE" . |
||
3190 | " fam.f_file = {$this->tree->getTreeId()} AND" . |
||
3191 | " wifedeath.d_gid = fam.f_wife AND" . |
||
3192 | " wifedeath.d_fact = 'DEAT' AND" . |
||
3193 | " married.d_gid = fam.f_id AND" . |
||
3194 | " married.d_fact = 'MARR' AND" . |
||
3195 | " married.d_julianday1 < wifedeath.d_julianday2 AND" . |
||
3196 | " married.d_julianday1 <> 0" . |
||
3197 | " GROUP BY family" . |
||
3198 | " ORDER BY age {$age_dir}"); |
||
3199 | $drows = $this->runSql( |
||
3200 | " SELECT SQL_CACHE DISTINCT fam.f_id AS family, MIN(divorced.d_julianday2-married.d_julianday1) AS age" . |
||
3201 | " FROM `##families` AS fam" . |
||
3202 | " LEFT JOIN `##dates` AS married ON married.d_file = {$this->tree->getTreeId()}" . |
||
3203 | " LEFT JOIN `##dates` AS divorced ON divorced.d_file = {$this->tree->getTreeId()}" . |
||
3204 | " WHERE" . |
||
3205 | " fam.f_file = {$this->tree->getTreeId()} AND" . |
||
3206 | " married.d_gid = fam.f_id AND" . |
||
3207 | " married.d_fact = 'MARR' AND" . |
||
3208 | " divorced.d_gid = fam.f_id AND" . |
||
3209 | " divorced.d_fact IN ('DIV', 'ANUL', '_SEPR', '_DETS') AND" . |
||
3210 | " married.d_julianday1 < divorced.d_julianday2 AND" . |
||
3211 | " married.d_julianday1 <> 0" . |
||
3212 | " GROUP BY family" . |
||
3213 | " ORDER BY age {$age_dir}"); |
||
3214 | if (!isset($hrows) && !isset($wrows) && !isset($drows)) { |
||
3215 | return ''; |
||
3216 | } |
||
3217 | $rows = []; |
||
3218 | foreach ($drows as $family) { |
||
3219 | $rows[$family['family']] = $family['age']; |
||
3220 | } |
||
3221 | foreach ($hrows as $family) { |
||
3222 | if (!isset($rows[$family['family']])) { |
||
3223 | $rows[$family['family']] = $family['age']; |
||
3224 | } |
||
3225 | } |
||
3226 | foreach ($wrows as $family) { |
||
3227 | if (!isset($rows[$family['family']])) { |
||
3228 | $rows[$family['family']] = $family['age']; |
||
3229 | } elseif ($rows[$family['family']] > $family['age']) { |
||
3230 | $rows[$family['family']] = $family['age']; |
||
3231 | } |
||
3232 | } |
||
3233 | if ($age_dir === 'DESC') { |
||
3234 | arsort($rows); |
||
3235 | } else { |
||
3236 | asort($rows); |
||
3237 | } |
||
3238 | $top10 = []; |
||
3239 | $i = 0; |
||
3240 | foreach ($rows as $fam => $age) { |
||
3241 | $family = Family::getInstance($fam, $this->tree); |
||
3242 | if ($type === 'name') { |
||
3243 | return $family->formatList(); |
||
3244 | } |
||
3245 | if ((int) ($age / 365.25) > 0) { |
||
3246 | $age = (int) ($age / 365.25) . 'y'; |
||
3247 | } elseif ((int) ($age / 30.4375) > 0) { |
||
3248 | $age = (int) ($age / 30.4375) . 'm'; |
||
3249 | } else { |
||
3250 | $age = $age . 'd'; |
||
3251 | } |
||
3252 | $age = FunctionsDate::getAgeAtEvent($age); |
||
3253 | if ($type === 'age') { |
||
3254 | return $age; |
||
3255 | } |
||
3256 | $husb = $family->getHusband(); |
||
3257 | $wife = $family->getWife(); |
||
3258 | if ($husb && $wife && ($husb->getAllDeathDates() && $wife->getAllDeathDates() || !$husb->isDead() || !$wife->isDead())) { |
||
3259 | if ($family->canShow()) { |
||
3260 | if ($type === 'list') { |
||
3261 | $top10[] = '<li><a href="' . e($family->url()) . '">' . $family->getFullName() . '</a> (' . $age . ')' . '</li>'; |
||
3262 | } else { |
||
3263 | $top10[] = '<a href="' . e($family->url()) . '">' . $family->getFullName() . '</a> (' . $age . ')'; |
||
3264 | } |
||
3265 | } |
||
3266 | if (++$i === $total) { |
||
3267 | break; |
||
3268 | } |
||
3269 | } |
||
3270 | } |
||
3271 | if ($type === 'list') { |
||
3272 | $top10 = implode('', $top10); |
||
3273 | } else { |
||
3274 | $top10 = implode('; ', $top10); |
||
3275 | } |
||
3276 | if (I18N::direction() === 'rtl') { |
||
3277 | $top10 = str_replace(['[', ']', '(', ')', '+'], ['‏[', '‏]', '‏(', '‏)', '‏+'], $top10); |
||
3278 | } |
||
3279 | if ($type === 'list') { |
||
3280 | return '<ul>' . $top10 . '</ul>'; |
||
3281 | } |
||
3282 | |||
3283 | return $top10; |
||
3284 | } |
||
3285 | |||
3286 | /** |
||
3287 | * Find the ages between spouses. |
||
3288 | * |
||
3289 | * @param string $type |
||
3290 | * @param string $age_dir |
||
3291 | * @param string[] $params |
||
3292 | * |
||
3293 | * @return string |
||
3294 | */ |
||
3295 | private function ageBetweenSpousesQuery($type = 'list', $age_dir = 'DESC', $params = []) { |
||
3296 | if (isset($params[0])) { |
||
3297 | $total = (int) $params[0]; |
||
3298 | } else { |
||
3299 | $total = 10; |
||
3300 | } |
||
3301 | if ($age_dir === 'DESC') { |
||
3302 | $sql = |
||
3303 | "SELECT SQL_CACHE f_id AS xref, MIN(wife.d_julianday2-husb.d_julianday1) AS age" . |
||
3304 | " FROM `##families`" . |
||
3305 | " JOIN `##dates` AS wife ON wife.d_gid = f_wife AND wife.d_file = f_file" . |
||
3306 | " JOIN `##dates` AS husb ON husb.d_gid = f_husb AND husb.d_file = f_file" . |
||
3307 | " WHERE f_file = :tree_id" . |
||
3308 | " AND husb.d_fact = 'BIRT'" . |
||
3309 | " AND wife.d_fact = 'BIRT'" . |
||
3310 | " AND wife.d_julianday2 >= husb.d_julianday1 AND husb.d_julianday1 <> 0" . |
||
3311 | " GROUP BY xref" . |
||
3312 | " ORDER BY age DESC" . |
||
3313 | " LIMIT :limit"; |
||
3314 | } else { |
||
3315 | $sql = |
||
3316 | "SELECT SQL_CACHE f_id AS xref, MIN(husb.d_julianday2-wife.d_julianday1) AS age" . |
||
3317 | " FROM `##families`" . |
||
3318 | " JOIN `##dates` AS wife ON wife.d_gid = f_wife AND wife.d_file = f_file" . |
||
3319 | " JOIN `##dates` AS husb ON husb.d_gid = f_husb AND husb.d_file = f_file" . |
||
3320 | " WHERE f_file = :tree_id" . |
||
3321 | " AND husb.d_fact = 'BIRT'" . |
||
3322 | " AND wife.d_fact = 'BIRT'" . |
||
3323 | " AND husb.d_julianday2 >= wife.d_julianday1 AND wife.d_julianday1 <> 0" . |
||
3324 | " GROUP BY xref" . |
||
3325 | " ORDER BY age DESC" . |
||
3326 | " LIMIT :limit"; |
||
3327 | } |
||
3328 | $rows = Database::prepare( |
||
3329 | $sql |
||
3330 | )->execute([ |
||
3331 | 'tree_id' => $this->tree->getTreeId(), |
||
3332 | 'limit' => $total, |
||
3333 | ])->fetchAll(); |
||
3334 | |||
3335 | $top10 = []; |
||
3336 | foreach ($rows as $fam) { |
||
3337 | $family = Family::getInstance($fam->xref, $this->tree); |
||
3338 | if ($fam->age < 0) { |
||
3339 | break; |
||
3340 | } |
||
3341 | $age = $fam->age; |
||
3342 | if ((int) ($age / 365.25) > 0) { |
||
3343 | $age = (int) ($age / 365.25) . 'y'; |
||
3344 | } elseif ((int) ($age / 30.4375) > 0) { |
||
3345 | $age = (int) ($age / 30.4375) . 'm'; |
||
3346 | } else { |
||
3347 | $age = $age . 'd'; |
||
3348 | } |
||
3349 | $age = FunctionsDate::getAgeAtEvent($age); |
||
3350 | if ($family->canShow()) { |
||
3351 | if ($type === 'list') { |
||
3352 | $top10[] = '<li><a href="' . e($family->url()) . '">' . $family->getFullName() . '</a> (' . $age . ')' . '</li>'; |
||
3353 | } else { |
||
3354 | $top10[] = '<a href="' . e($family->url()) . '">' . $family->getFullName() . '</a> (' . $age . ')'; |
||
3355 | } |
||
3356 | } |
||
3357 | } |
||
3358 | if ($type === 'list') { |
||
3359 | $top10 = implode('', $top10); |
||
3360 | if ($top10) { |
||
3361 | $top10 = '<ul>' . $top10 . '</ul>'; |
||
3362 | } |
||
3363 | } else { |
||
3364 | $top10 = implode(' ', $top10); |
||
3365 | } |
||
3366 | |||
3367 | return $top10; |
||
3368 | } |
||
3369 | |||
3370 | /** |
||
3371 | * General query on parents. |
||
3372 | * |
||
3373 | * @param string $type |
||
3374 | * @param string $age_dir |
||
3375 | * @param string $sex |
||
3376 | * @param bool $show_years |
||
3377 | * |
||
3378 | * @return string |
||
3379 | */ |
||
3380 | private function parentsQuery($type = 'full', $age_dir = 'ASC', $sex = 'F', $show_years = false) { |
||
3381 | if ($sex == 'F') { |
||
3382 | $sex_field = 'WIFE'; |
||
3383 | } else { |
||
3384 | $sex_field = 'HUSB'; |
||
3385 | } |
||
3386 | if ($age_dir != 'ASC') { |
||
3387 | $age_dir = 'DESC'; |
||
3388 | } |
||
3389 | $rows = $this->runSql( |
||
3390 | " SELECT SQL_CACHE" . |
||
3391 | " parentfamily.l_to AS id," . |
||
3392 | " childbirth.d_julianday2-birth.d_julianday1 AS age" . |
||
3393 | " FROM `##link` AS parentfamily" . |
||
3394 | " JOIN `##link` AS childfamily ON childfamily.l_file = {$this->tree->getTreeId()}" . |
||
3395 | " JOIN `##dates` AS birth ON birth.d_file = {$this->tree->getTreeId()}" . |
||
3396 | " JOIN `##dates` AS childbirth ON childbirth.d_file = {$this->tree->getTreeId()}" . |
||
3397 | " WHERE" . |
||
3398 | " birth.d_gid = parentfamily.l_to AND" . |
||
3399 | " childfamily.l_to = childbirth.d_gid AND" . |
||
3400 | " childfamily.l_type = 'CHIL' AND" . |
||
3401 | " parentfamily.l_type = '{$sex_field}' AND" . |
||
3402 | " childfamily.l_from = parentfamily.l_from AND" . |
||
3403 | " parentfamily.l_file = {$this->tree->getTreeId()} AND" . |
||
3404 | " birth.d_fact = 'BIRT' AND" . |
||
3405 | " childbirth.d_fact = 'BIRT' AND" . |
||
3406 | " birth.d_julianday1 <> 0 AND" . |
||
3407 | " childbirth.d_julianday2 > birth.d_julianday1" . |
||
3408 | " ORDER BY age {$age_dir} LIMIT 1" |
||
3409 | ); |
||
3410 | if (!isset($rows[0])) { |
||
3411 | return ''; |
||
3412 | } |
||
3413 | $row = $rows[0]; |
||
3414 | if (isset($row['id'])) { |
||
3415 | $person = Individual::getInstance($row['id'], $this->tree); |
||
3416 | } |
||
3417 | switch ($type) { |
||
3418 | default: |
||
3419 | case 'full': |
||
3420 | if ($person->canShow()) { |
||
3421 | $result = $person->formatList(); |
||
3422 | } else { |
||
3423 | $result = I18N::translate('This information is private and cannot be shown.'); |
||
3424 | } |
||
3425 | break; |
||
3426 | case 'name': |
||
3427 | $result = '<a href="' . e($person->url()) . '">' . $person->getFullName() . '</a>'; |
||
3428 | break; |
||
3429 | case 'age': |
||
3430 | $age = $row['age']; |
||
3431 | if ($show_years) { |
||
3432 | if ((int) ($age / 365.25) > 0) { |
||
3433 | $age = (int) ($age / 365.25) . 'y'; |
||
3434 | } elseif ((int) ($age / 30.4375) > 0) { |
||
3435 | $age = (int) ($age / 30.4375) . 'm'; |
||
3436 | } else { |
||
3437 | $age = $age . 'd'; |
||
3438 | } |
||
3439 | $result = FunctionsDate::getAgeAtEvent($age); |
||
3440 | } else { |
||
3441 | $result = (int) ($age / 365.25); |
||
3442 | } |
||
3443 | break; |
||
3444 | } |
||
3445 | |||
3446 | return $result; |
||
3447 | } |
||
3448 | |||
3449 | /** |
||
3450 | * General query on marriages. |
||
3451 | * |
||
3452 | * @param bool $simple |
||
3453 | * @param bool $first |
||
3454 | * @param int $year1 |
||
3455 | * @param int $year2 |
||
3456 | * @param string[] $params |
||
3457 | * |
||
3458 | * @return string|array |
||
3459 | */ |
||
3460 | public function statsMarrQuery($simple = true, $first = false, $year1 = -1, $year2 = -1, $params = []) { |
||
3461 | $WT_STATS_CHART_COLOR1 = Theme::theme()->parameter('distribution-chart-no-values'); |
||
3462 | $WT_STATS_CHART_COLOR2 = Theme::theme()->parameter('distribution-chart-high-values'); |
||
3463 | $WT_STATS_S_CHART_X = Theme::theme()->parameter('stats-small-chart-x'); |
||
3464 | $WT_STATS_S_CHART_Y = Theme::theme()->parameter('stats-small-chart-y'); |
||
3465 | |||
3466 | if ($simple) { |
||
3467 | $sql = |
||
3468 | "SELECT SQL_CACHE FLOOR(d_year/100+1) AS century, COUNT(*) AS total" . |
||
3469 | " FROM `##dates`" . |
||
3470 | " WHERE d_file={$this->tree->getTreeId()} AND d_year<>0 AND d_fact='MARR' AND d_type IN ('@#DGREGORIAN@', '@#DJULIAN@')"; |
||
3471 | if ($year1 >= 0 && $year2 >= 0) { |
||
3472 | $sql .= " AND d_year BETWEEN '{$year1}' AND '{$year2}'"; |
||
3473 | } |
||
3474 | $sql .= " GROUP BY century ORDER BY century"; |
||
3475 | } elseif ($first) { |
||
3476 | $years = ''; |
||
3477 | if ($year1 >= 0 && $year2 >= 0) { |
||
3478 | $years = " married.d_year BETWEEN '{$year1}' AND '{$year2}' AND"; |
||
3479 | } |
||
3480 | $sql = |
||
3481 | " SELECT SQL_CACHE fam.f_id AS fams, fam.f_husb, fam.f_wife, married.d_julianday2 AS age, married.d_month AS month, indi.i_id AS indi" . |
||
3482 | " FROM `##families` AS fam" . |
||
3483 | " LEFT JOIN `##dates` AS married ON married.d_file = {$this->tree->getTreeId()}" . |
||
3484 | " LEFT JOIN `##individuals` AS indi ON indi.i_file = {$this->tree->getTreeId()}" . |
||
3485 | " WHERE" . |
||
3486 | " married.d_gid = fam.f_id AND" . |
||
3487 | " fam.f_file = {$this->tree->getTreeId()} AND" . |
||
3488 | " married.d_fact = 'MARR' AND" . |
||
3489 | " married.d_julianday2 <> 0 AND" . |
||
3490 | $years . |
||
3491 | " (indi.i_id = fam.f_husb OR indi.i_id = fam.f_wife)" . |
||
3492 | " ORDER BY fams, indi, age ASC"; |
||
3493 | } else { |
||
3494 | $sql = |
||
3495 | "SELECT SQL_CACHE d_month, COUNT(*) AS total" . |
||
3496 | " FROM `##dates`" . |
||
3497 | " WHERE d_file={$this->tree->getTreeId()} AND d_fact='MARR'"; |
||
3498 | if ($year1 >= 0 && $year2 >= 0) { |
||
3499 | $sql .= " AND d_year BETWEEN '{$year1}' AND '{$year2}'"; |
||
3500 | } |
||
3501 | $sql .= " GROUP BY d_month"; |
||
3502 | } |
||
3503 | $rows = $this->runSql($sql); |
||
3504 | if (!isset($rows)) { |
||
3505 | return ''; |
||
3506 | } |
||
3507 | if ($simple) { |
||
3508 | if (isset($params[0]) && $params[0] != '') { |
||
3509 | $size = strtolower($params[0]); |
||
3510 | } else { |
||
3511 | $size = $WT_STATS_S_CHART_X . 'x' . $WT_STATS_S_CHART_Y; |
||
3512 | } |
||
3513 | if (isset($params[1]) && $params[1] != '') { |
||
3514 | $color_from = strtolower($params[1]); |
||
3515 | } else { |
||
3516 | $color_from = $WT_STATS_CHART_COLOR1; |
||
3517 | } |
||
3518 | if (isset($params[2]) && $params[2] != '') { |
||
3519 | $color_to = strtolower($params[2]); |
||
3520 | } else { |
||
3521 | $color_to = $WT_STATS_CHART_COLOR2; |
||
3522 | } |
||
3523 | $sizes = explode('x', $size); |
||
3524 | $tot = 0; |
||
3525 | foreach ($rows as $values) { |
||
3526 | $tot += (int) $values['total']; |
||
3527 | } |
||
3528 | // Beware divide by zero |
||
3529 | if ($tot === 0) { |
||
3530 | return ''; |
||
3531 | } |
||
3532 | $centuries = ''; |
||
3533 | $counts = []; |
||
3534 | foreach ($rows as $values) { |
||
3535 | $counts[] = round(100 * $values['total'] / $tot, 0); |
||
3536 | $centuries .= $this->centuryName($values['century']) . ' - ' . I18N::number($values['total']) . '|'; |
||
3537 | } |
||
3538 | $chd = $this->arrayToExtendedEncoding($counts); |
||
3539 | $chl = substr($centuries, 0, -1); |
||
3540 | |||
3541 | return "<img src=\"https://chart.googleapis.com/chart?cht=p3&chd=e:{$chd}&chs={$size}&chco={$color_from},{$color_to}&chf=bg,s,ffffff00&chl={$chl}\" width=\"{$sizes[0]}\" height=\"{$sizes[1]}\" alt=\"" . I18N::translate('Marriages by century') . '" title="' . I18N::translate('Marriages by century') . '" />'; |
||
3542 | } |
||
3543 | |||
3544 | return $rows; |
||
3545 | } |
||
3546 | |||
3547 | /** |
||
3548 | * General query on divorces. |
||
3549 | * |
||
3550 | * @param bool $simple |
||
3551 | * @param bool $first |
||
3552 | * @param int $year1 |
||
3553 | * @param int $year2 |
||
3554 | * @param string[] $params |
||
3555 | * |
||
3556 | * @return string|array |
||
3557 | */ |
||
3558 | private function statsDivQuery($simple = true, $first = false, $year1 = -1, $year2 = -1, $params = []) { |
||
3559 | $WT_STATS_CHART_COLOR1 = Theme::theme()->parameter('distribution-chart-no-values'); |
||
3560 | $WT_STATS_CHART_COLOR2 = Theme::theme()->parameter('distribution-chart-high-values'); |
||
3561 | $WT_STATS_S_CHART_X = Theme::theme()->parameter('stats-small-chart-x'); |
||
3562 | $WT_STATS_S_CHART_Y = Theme::theme()->parameter('stats-small-chart-y'); |
||
3563 | |||
3564 | if ($simple) { |
||
3565 | $sql = |
||
3566 | "SELECT SQL_CACHE FLOOR(d_year/100+1) AS century, COUNT(*) AS total" . |
||
3567 | " FROM `##dates`" . |
||
3568 | " WHERE d_file={$this->tree->getTreeId()} AND d_year<>0 AND d_fact = 'DIV' AND d_type IN ('@#DGREGORIAN@', '@#DJULIAN@')"; |
||
3569 | if ($year1 >= 0 && $year2 >= 0) { |
||
3570 | $sql .= " AND d_year BETWEEN '{$year1}' AND '{$year2}'"; |
||
3571 | } |
||
3572 | $sql .= " GROUP BY century ORDER BY century"; |
||
3573 | } elseif ($first) { |
||
3574 | $years = ''; |
||
3575 | if ($year1 >= 0 && $year2 >= 0) { |
||
3576 | $years = " divorced.d_year BETWEEN '{$year1}' AND '{$year2}' AND"; |
||
3577 | } |
||
3578 | $sql = |
||
3579 | " SELECT SQL_CACHE fam.f_id AS fams, fam.f_husb, fam.f_wife, divorced.d_julianday2 AS age, divorced.d_month AS month, indi.i_id AS indi" . |
||
3580 | " FROM `##families` AS fam" . |
||
3581 | " LEFT JOIN `##dates` AS divorced ON divorced.d_file = {$this->tree->getTreeId()}" . |
||
3582 | " LEFT JOIN `##individuals` AS indi ON indi.i_file = {$this->tree->getTreeId()}" . |
||
3583 | " WHERE" . |
||
3584 | " divorced.d_gid = fam.f_id AND" . |
||
3585 | " fam.f_file = {$this->tree->getTreeId()} AND" . |
||
3586 | " divorced.d_fact = 'DIV' AND" . |
||
3587 | " divorced.d_julianday2 <> 0 AND" . |
||
3588 | $years . |
||
3589 | " (indi.i_id = fam.f_husb OR indi.i_id = fam.f_wife)" . |
||
3590 | " ORDER BY fams, indi, age ASC"; |
||
3591 | } else { |
||
3592 | $sql = |
||
3593 | "SELECT SQL_CACHE d_month, COUNT(*) AS total FROM `##dates` " . |
||
3594 | "WHERE d_file={$this->tree->getTreeId()} AND d_fact = 'DIV'"; |
||
3595 | if ($year1 >= 0 && $year2 >= 0) { |
||
3596 | $sql .= " AND d_year BETWEEN '{$year1}' AND '{$year2}'"; |
||
3597 | } |
||
3598 | $sql .= " GROUP BY d_month"; |
||
3599 | } |
||
3600 | $rows = $this->runSql($sql); |
||
3601 | if (!isset($rows)) { |
||
3602 | return ''; |
||
3603 | } |
||
3604 | if ($simple) { |
||
3605 | if (isset($params[0]) && $params[0] != '') { |
||
3606 | $size = strtolower($params[0]); |
||
3607 | } else { |
||
3608 | $size = $WT_STATS_S_CHART_X . 'x' . $WT_STATS_S_CHART_Y; |
||
3609 | } |
||
3610 | if (isset($params[1]) && $params[1] != '') { |
||
3611 | $color_from = strtolower($params[1]); |
||
3612 | } else { |
||
3613 | $color_from = $WT_STATS_CHART_COLOR1; |
||
3614 | } |
||
3615 | if (isset($params[2]) && $params[2] != '') { |
||
3616 | $color_to = strtolower($params[2]); |
||
3617 | } else { |
||
3618 | $color_to = $WT_STATS_CHART_COLOR2; |
||
3619 | } |
||
3620 | $sizes = explode('x', $size); |
||
3621 | $tot = 0; |
||
3622 | foreach ($rows as $values) { |
||
3623 | $tot += (int) $values['total']; |
||
3624 | } |
||
3625 | // Beware divide by zero |
||
3626 | if ($tot === 0) { |
||
3627 | return ''; |
||
3628 | } |
||
3629 | $centuries = ''; |
||
3630 | $counts = []; |
||
3631 | foreach ($rows as $values) { |
||
3632 | $counts[] = round(100 * $values['total'] / $tot, 0); |
||
3633 | $centuries .= $this->centuryName($values['century']) . ' - ' . I18N::number($values['total']) . '|'; |
||
3634 | } |
||
3635 | $chd = $this->arrayToExtendedEncoding($counts); |
||
3636 | $chl = substr($centuries, 0, -1); |
||
3637 | |||
3638 | return "<img src=\"https://chart.googleapis.com/chart?cht=p3&chd=e:{$chd}&chs={$size}&chco={$color_from},{$color_to}&chf=bg,s,ffffff00&chl={$chl}\" width=\"{$sizes[0]}\" height=\"{$sizes[1]}\" alt=\"" . I18N::translate('Divorces by century') . '" title="' . I18N::translate('Divorces by century') . '" />'; |
||
3639 | } |
||
3640 | |||
3641 | return $rows; |
||
3642 | } |
||
3643 | |||
3644 | /** |
||
3645 | * Find the earliest marriage. |
||
3646 | * |
||
3647 | * @return string |
||
3648 | */ |
||
3649 | public function firstMarriage() { |
||
3650 | return $this->mortalityQuery('full', 'ASC', 'MARR'); |
||
3651 | } |
||
3652 | |||
3653 | /** |
||
3654 | * Find the year of the earliest marriage. |
||
3655 | * |
||
3656 | * @return string |
||
3657 | */ |
||
3658 | public function firstMarriageYear() { |
||
3659 | return $this->mortalityQuery('year', 'ASC', 'MARR'); |
||
3660 | } |
||
3661 | |||
3662 | /** |
||
3663 | * Find the names of spouses of the earliest marriage. |
||
3664 | * |
||
3665 | * @return string |
||
3666 | */ |
||
3667 | public function firstMarriageName() { |
||
3668 | return $this->mortalityQuery('name', 'ASC', 'MARR'); |
||
3669 | } |
||
3670 | |||
3671 | /** |
||
3672 | * Find the place of the earliest marriage. |
||
3673 | * |
||
3674 | * @return string |
||
3675 | */ |
||
3676 | public function firstMarriagePlace() { |
||
3677 | return $this->mortalityQuery('place', 'ASC', 'MARR'); |
||
3678 | } |
||
3679 | |||
3680 | /** |
||
3681 | * Find the latest marriage. |
||
3682 | * |
||
3683 | * @return string |
||
3684 | */ |
||
3685 | public function lastMarriage() { |
||
3686 | return $this->mortalityQuery('full', 'DESC', 'MARR'); |
||
3687 | } |
||
3688 | |||
3689 | /** |
||
3690 | * Find the year of the latest marriage. |
||
3691 | * |
||
3692 | * @return string |
||
3693 | */ |
||
3694 | public function lastMarriageYear() { |
||
3695 | return $this->mortalityQuery('year', 'DESC', 'MARR'); |
||
3696 | } |
||
3697 | |||
3698 | /** |
||
3699 | * Find the names of spouses of the latest marriage. |
||
3700 | * |
||
3701 | * @return string |
||
3702 | */ |
||
3703 | public function lastMarriageName() { |
||
3704 | return $this->mortalityQuery('name', 'DESC', 'MARR'); |
||
3705 | } |
||
3706 | |||
3707 | /** |
||
3708 | * Find the location of the latest marriage. |
||
3709 | * |
||
3710 | * @return string |
||
3711 | */ |
||
3712 | public function lastMarriagePlace() { |
||
3713 | return $this->mortalityQuery('place', 'DESC', 'MARR'); |
||
3714 | } |
||
3715 | |||
3716 | /** |
||
3717 | * General query on marriages. |
||
3718 | * |
||
3719 | * @param string[] $params |
||
3720 | * |
||
3721 | * @return string |
||
3722 | */ |
||
3723 | public function statsMarr($params = []) { |
||
3724 | return $this->statsMarrQuery(true, false, -1, -1, $params); |
||
3725 | } |
||
3726 | |||
3727 | /** |
||
3728 | * Find the earliest divorce. |
||
3729 | * |
||
3730 | * @return string |
||
3731 | */ |
||
3732 | public function firstDivorce() { |
||
3733 | return $this->mortalityQuery('full', 'ASC', 'DIV'); |
||
3734 | } |
||
3735 | |||
3736 | /** |
||
3737 | * Find the year of the earliest divorce. |
||
3738 | * |
||
3739 | * @return string |
||
3740 | */ |
||
3741 | public function firstDivorceYear() { |
||
3742 | return $this->mortalityQuery('year', 'ASC', 'DIV'); |
||
3743 | } |
||
3744 | |||
3745 | /** |
||
3746 | * Find the names of individuals in the earliest divorce. |
||
3747 | * |
||
3748 | * @return string |
||
3749 | */ |
||
3750 | public function firstDivorceName() { |
||
3751 | return $this->mortalityQuery('name', 'ASC', 'DIV'); |
||
3752 | } |
||
3753 | |||
3754 | /** |
||
3755 | * Find the location of the earliest divorce. |
||
3756 | * |
||
3757 | * @return string |
||
3758 | */ |
||
3759 | public function firstDivorcePlace() { |
||
3760 | return $this->mortalityQuery('place', 'ASC', 'DIV'); |
||
3761 | } |
||
3762 | |||
3763 | /** |
||
3764 | * Find the latest divorce. |
||
3765 | * |
||
3766 | * @return string |
||
3767 | */ |
||
3768 | public function lastDivorce() { |
||
3769 | return $this->mortalityQuery('full', 'DESC', 'DIV'); |
||
3770 | } |
||
3771 | |||
3772 | /** |
||
3773 | * Find the year of the latest divorce. |
||
3774 | * |
||
3775 | * @return string |
||
3776 | */ |
||
3777 | public function lastDivorceYear() { |
||
3778 | return $this->mortalityQuery('year', 'DESC', 'DIV'); |
||
3779 | } |
||
3780 | |||
3781 | /** |
||
3782 | * Find the names of the individuals in the latest divorce. |
||
3783 | * |
||
3784 | * @return string |
||
3785 | */ |
||
3786 | public function lastDivorceName() { |
||
3787 | return $this->mortalityQuery('name', 'DESC', 'DIV'); |
||
3788 | } |
||
3789 | |||
3790 | /** |
||
3791 | * Find the location of the latest divorce. |
||
3792 | * |
||
3793 | * @return string |
||
3794 | */ |
||
3795 | public function lastDivorcePlace() { |
||
3796 | return $this->mortalityQuery('place', 'DESC', 'DIV'); |
||
3797 | } |
||
3798 | |||
3799 | /** |
||
3800 | * General divorce query. |
||
3801 | * |
||
3802 | * @param string[] $params |
||
3803 | * |
||
3804 | * @return string |
||
3805 | */ |
||
3806 | public function statsDiv($params = []) { |
||
3807 | return $this->statsDivQuery(true, false, -1, -1, $params); |
||
3808 | } |
||
3809 | |||
3810 | /** |
||
3811 | * General query on ages at marriage. |
||
3812 | * |
||
3813 | * @param bool $simple |
||
3814 | * @param string $sex |
||
3815 | * @param int $year1 |
||
3816 | * @param int $year2 |
||
3817 | * @param string[] $params |
||
3818 | * |
||
3819 | * @return array|string |
||
3820 | */ |
||
3821 | public function statsMarrAgeQuery($simple = true, $sex = 'M', $year1 = -1, $year2 = -1, $params = []) { |
||
3822 | if ($simple) { |
||
3823 | if (isset($params[0]) && $params[0] != '') { |
||
3824 | $size = strtolower($params[0]); |
||
3825 | } else { |
||
3826 | $size = '200x250'; |
||
3827 | } |
||
3828 | $sizes = explode('x', $size); |
||
3829 | $rows = $this->runSql( |
||
3830 | "SELECT SQL_CACHE " . |
||
3831 | " ROUND(AVG(married.d_julianday2-birth.d_julianday1-182.5)/365.25,1) AS age, " . |
||
3832 | " FLOOR(married.d_year/100+1) AS century, " . |
||
3833 | " 'M' AS sex " . |
||
3834 | "FROM `##dates` AS married " . |
||
3835 | "JOIN `##families` AS fam ON (married.d_gid=fam.f_id AND married.d_file=fam.f_file) " . |
||
3836 | "JOIN `##dates` AS birth ON (birth.d_gid=fam.f_husb AND birth.d_file=fam.f_file) " . |
||
3837 | "WHERE " . |
||
3838 | " '{$sex}' IN ('M', 'BOTH') AND " . |
||
3839 | " married.d_file={$this->tree->getTreeId()} AND married.d_type IN ('@#DGREGORIAN@', '@#DJULIAN@') AND married.d_fact='MARR' AND " . |
||
3840 | " birth.d_type IN ('@#DGREGORIAN@', '@#DJULIAN@') AND birth.d_fact='BIRT' AND " . |
||
3841 | " married.d_julianday1>birth.d_julianday1 AND birth.d_julianday1<>0 " . |
||
3842 | "GROUP BY century, sex " . |
||
3843 | "UNION ALL " . |
||
3844 | "SELECT " . |
||
3845 | " ROUND(AVG(married.d_julianday2-birth.d_julianday1-182.5)/365.25,1) AS age, " . |
||
3846 | " FLOOR(married.d_year/100+1) AS century, " . |
||
3847 | " 'F' AS sex " . |
||
3848 | "FROM `##dates` AS married " . |
||
3849 | "JOIN `##families` AS fam ON (married.d_gid=fam.f_id AND married.d_file=fam.f_file) " . |
||
3850 | "JOIN `##dates` AS birth ON (birth.d_gid=fam.f_wife AND birth.d_file=fam.f_file) " . |
||
3851 | "WHERE " . |
||
3852 | " '{$sex}' IN ('F', 'BOTH') AND " . |
||
3853 | " married.d_file={$this->tree->getTreeId()} AND married.d_type IN ('@#DGREGORIAN@', '@#DJULIAN@') AND married.d_fact='MARR' AND " . |
||
3854 | " birth.d_type IN ('@#DGREGORIAN@', '@#DJULIAN@') AND birth.d_fact='BIRT' AND " . |
||
3855 | " married.d_julianday1>birth.d_julianday1 AND birth.d_julianday1<>0 " . |
||
3856 | " GROUP BY century, sex ORDER BY century" |
||
3857 | ); |
||
3858 | if (empty($rows)) { |
||
3859 | return ''; |
||
3860 | } |
||
3861 | $max = 0; |
||
3862 | foreach ($rows as $values) { |
||
3863 | if ($max < $values['age']) { |
||
3864 | $max = $values['age']; |
||
3865 | } |
||
3866 | } |
||
3867 | $chxl = '0:|'; |
||
3868 | $chmm = ''; |
||
3869 | $chmf = ''; |
||
3870 | $i = 0; |
||
3871 | $countsm = ''; |
||
3872 | $countsf = ''; |
||
3873 | $countsa = ''; |
||
3874 | $out = []; |
||
3875 | foreach ($rows as $values) { |
||
3876 | $out[$values['century']][$values['sex']] = $values['age']; |
||
3877 | } |
||
3878 | foreach ($out as $century => $values) { |
||
3879 | if ($sizes[0] < 1000) { |
||
3880 | $sizes[0] += 50; |
||
3881 | } |
||
3882 | $chxl .= $this->centuryName($century) . '|'; |
||
3883 | $average = 0; |
||
3884 | if (isset($values['F'])) { |
||
3885 | if ($max <= 50) { |
||
3886 | $value = $values['F'] * 2; |
||
3887 | } else { |
||
3888 | $value = $values['F']; |
||
3889 | } |
||
3890 | $countsf .= $value . ','; |
||
3891 | $average = $value; |
||
3892 | $chmf .= 't' . $values['F'] . ',000000,1,' . $i . ',11,1|'; |
||
3893 | } else { |
||
3894 | $countsf .= '0,'; |
||
3895 | $chmf .= 't0,000000,1,' . $i . ',11,1|'; |
||
3896 | } |
||
3897 | if (isset($values['M'])) { |
||
3898 | if ($max <= 50) { |
||
3899 | $value = $values['M'] * 2; |
||
3900 | } else { |
||
3901 | $value = $values['M']; |
||
3902 | } |
||
3903 | $countsm .= $value . ','; |
||
3904 | if ($average == 0) { |
||
3905 | $countsa .= $value . ','; |
||
3906 | } else { |
||
3907 | $countsa .= (($value + $average) / 2) . ','; |
||
3908 | } |
||
3909 | $chmm .= 't' . $values['M'] . ',000000,0,' . $i . ',11,1|'; |
||
3910 | } else { |
||
3911 | $countsm .= '0,'; |
||
3912 | if ($average == 0) { |
||
3913 | $countsa .= '0,'; |
||
3914 | } else { |
||
3915 | $countsa .= $value . ','; |
||
3916 | } |
||
3917 | $chmm .= 't0,000000,0,' . $i . ',11,1|'; |
||
3918 | } |
||
3919 | $i++; |
||
3920 | } |
||
3921 | $countsm = substr($countsm, 0, -1); |
||
3922 | $countsf = substr($countsf, 0, -1); |
||
3923 | $countsa = substr($countsa, 0, -1); |
||
3924 | $chmf = substr($chmf, 0, -1); |
||
3925 | $chd = 't2:' . $countsm . '|' . $countsf . '|' . $countsa; |
||
3926 | if ($max <= 50) { |
||
3927 | $chxl .= '1:||' . I18N::translate('century') . '|2:|0|10|20|30|40|50|3:||' . I18N::translate('Age') . '|'; |
||
3928 | } else { |
||
3929 | $chxl .= '1:||' . I18N::translate('century') . '|2:|0|10|20|30|40|50|60|70|80|90|100|3:||' . I18N::translate('Age') . '|'; |
||
3930 | } |
||
3931 | if (count($rows) > 4 || mb_strlen(I18N::translate('Average age in century of marriage')) < 30) { |
||
3932 | $chtt = I18N::translate('Average age in century of marriage'); |
||
3933 | } else { |
||
3934 | $offset = 0; |
||
3935 | $counter = []; |
||
3936 | while ($offset = strpos(I18N::translate('Average age in century of marriage'), ' ', $offset + 1)) { |
||
3937 | $counter[] = $offset; |
||
3938 | } |
||
3939 | $half = (int) (count($counter) / 2); |
||
3940 | $chtt = substr_replace(I18N::translate('Average age in century of marriage'), '|', $counter[$half], 1); |
||
3941 | } |
||
3942 | |||
3943 | return '<img src="' . "https://chart.googleapis.com/chart?cht=bvg&chs={$sizes[0]}x{$sizes[1]}&chm=D,FF0000,2,0,3,1|{$chmm}{$chmf}&chf=bg,s,ffffff00|c,s,ffffff00&chtt=" . rawurlencode($chtt) . "&chd={$chd}&chco=0000FF,FFA0CB,FF0000&chbh=20,3&chxt=x,x,y,y&chxl=" . rawurlencode($chxl) . '&chdl=' . rawurlencode(I18N::translate('Males') . '|' . I18N::translate('Females') . '|' . I18N::translate('Average age')) . "\" width=\"{$sizes[0]}\" height=\"{$sizes[1]}\" alt=\"" . I18N::translate('Average age in century of marriage') . '" title="' . I18N::translate('Average age in century of marriage') . '" />'; |
||
3944 | } else { |
||
3945 | if ($year1 >= 0 && $year2 >= 0) { |
||
3946 | $years = " married.d_year BETWEEN {$year1} AND {$year2} AND "; |
||
3947 | } else { |
||
3948 | $years = ''; |
||
3949 | } |
||
3950 | $rows = $this->runSql( |
||
3951 | "SELECT SQL_CACHE " . |
||
3952 | " fam.f_id, " . |
||
3953 | " birth.d_gid, " . |
||
3954 | " married.d_julianday2-birth.d_julianday1 AS age " . |
||
3955 | "FROM `##dates` AS married " . |
||
3956 | "JOIN `##families` AS fam ON (married.d_gid=fam.f_id AND married.d_file=fam.f_file) " . |
||
3957 | "JOIN `##dates` AS birth ON (birth.d_gid=fam.f_husb AND birth.d_file=fam.f_file) " . |
||
3958 | "WHERE " . |
||
3959 | " '{$sex}' IN ('M', 'BOTH') AND {$years} " . |
||
3960 | " married.d_file={$this->tree->getTreeId()} AND married.d_type IN ('@#DGREGORIAN@', '@#DJULIAN@') AND married.d_fact='MARR' AND " . |
||
3961 | " birth.d_type IN ('@#DGREGORIAN@', '@#DJULIAN@') AND birth.d_fact='BIRT' AND " . |
||
3962 | " married.d_julianday1>birth.d_julianday1 AND birth.d_julianday1<>0 " . |
||
3963 | "UNION ALL " . |
||
3964 | "SELECT " . |
||
3965 | " fam.f_id, " . |
||
3966 | " birth.d_gid, " . |
||
3967 | " married.d_julianday2-birth.d_julianday1 AS age " . |
||
3968 | "FROM `##dates` AS married " . |
||
3969 | "JOIN `##families` AS fam ON (married.d_gid=fam.f_id AND married.d_file=fam.f_file) " . |
||
3970 | "JOIN `##dates` AS birth ON (birth.d_gid=fam.f_wife AND birth.d_file=fam.f_file) " . |
||
3971 | "WHERE " . |
||
3972 | " '{$sex}' IN ('F', 'BOTH') AND {$years} " . |
||
3973 | " married.d_file={$this->tree->getTreeId()} AND married.d_type IN ('@#DGREGORIAN@', '@#DJULIAN@') AND married.d_fact='MARR' AND " . |
||
3974 | " birth.d_type IN ('@#DGREGORIAN@', '@#DJULIAN@') AND birth.d_fact='BIRT' AND " . |
||
3975 | " married.d_julianday1>birth.d_julianday1 AND birth.d_julianday1<>0 " |
||
3976 | ); |
||
3977 | |||
3978 | return $rows; |
||
3979 | } |
||
3980 | } |
||
3981 | |||
3982 | /** |
||
3983 | * Find the youngest wife. |
||
3984 | * |
||
3985 | * @return string |
||
3986 | */ |
||
3987 | public function youngestMarriageFemale() { |
||
3988 | return $this->marriageQuery('full', 'ASC', 'F', false); |
||
3989 | } |
||
3990 | |||
3991 | /** |
||
3992 | * Find the name of the youngest wife. |
||
3993 | * |
||
3994 | * @return string |
||
3995 | */ |
||
3996 | public function youngestMarriageFemaleName() { |
||
3997 | return $this->marriageQuery('name', 'ASC', 'F', false); |
||
3998 | } |
||
3999 | |||
4000 | /** |
||
4001 | * Find the age of the youngest wife. |
||
4002 | * |
||
4003 | * @param bool $show_years |
||
4004 | * |
||
4005 | * @return string |
||
4006 | */ |
||
4007 | public function youngestMarriageFemaleAge($show_years = false) { |
||
4008 | return $this->marriageQuery('age', 'ASC', 'F', $show_years); |
||
4009 | } |
||
4010 | |||
4011 | /** |
||
4012 | * Find the oldest wife. |
||
4013 | * |
||
4014 | * @return string |
||
4015 | */ |
||
4016 | public function oldestMarriageFemale() { |
||
4017 | return $this->marriageQuery('full', 'DESC', 'F', false); |
||
4018 | } |
||
4019 | |||
4020 | /** |
||
4021 | * Find the name of the oldest wife. |
||
4022 | * |
||
4023 | * @return string |
||
4024 | */ |
||
4025 | public function oldestMarriageFemaleName() { |
||
4026 | return $this->marriageQuery('name', 'DESC', 'F', false); |
||
4027 | } |
||
4028 | |||
4029 | /** |
||
4030 | * Find the age of the oldest wife. |
||
4031 | * |
||
4032 | * @param bool $show_years |
||
4033 | * |
||
4034 | * @return string |
||
4035 | */ |
||
4036 | public function oldestMarriageFemaleAge($show_years = false) { |
||
4037 | return $this->marriageQuery('age', 'DESC', 'F', $show_years); |
||
4038 | } |
||
4039 | |||
4040 | /** |
||
4041 | * Find the youngest husband. |
||
4042 | * |
||
4043 | * @return string |
||
4044 | */ |
||
4045 | public function youngestMarriageMale() { |
||
4046 | return $this->marriageQuery('full', 'ASC', 'M', false); |
||
4047 | } |
||
4048 | |||
4049 | /** |
||
4050 | * Find the name of the youngest husband. |
||
4051 | * |
||
4052 | * @return string |
||
4053 | */ |
||
4054 | public function youngestMarriageMaleName() { |
||
4055 | return $this->marriageQuery('name', 'ASC', 'M', false); |
||
4056 | } |
||
4057 | |||
4058 | /** |
||
4059 | * Find the age of the youngest husband. |
||
4060 | * |
||
4061 | * @param bool $show_years |
||
4062 | * |
||
4063 | * @return string |
||
4064 | */ |
||
4065 | public function youngestMarriageMaleAge($show_years = false) { |
||
4066 | return $this->marriageQuery('age', 'ASC', 'M', $show_years); |
||
4067 | } |
||
4068 | |||
4069 | /** |
||
4070 | * Find the oldest husband. |
||
4071 | * |
||
4072 | * @return string |
||
4073 | */ |
||
4074 | public function oldestMarriageMale() { |
||
4075 | return $this->marriageQuery('full', 'DESC', 'M', false); |
||
4076 | } |
||
4077 | |||
4078 | /** |
||
4079 | * Find the name of the oldest husband. |
||
4080 | * |
||
4081 | * @return string |
||
4082 | */ |
||
4083 | public function oldestMarriageMaleName() { |
||
4084 | return $this->marriageQuery('name', 'DESC', 'M', false); |
||
4085 | } |
||
4086 | |||
4087 | /** |
||
4088 | * Find the age of the oldest husband. |
||
4089 | * |
||
4090 | * @param bool $show_years |
||
4091 | * |
||
4092 | * @return string |
||
4093 | */ |
||
4094 | public function oldestMarriageMaleAge($show_years = false) { |
||
4095 | return $this->marriageQuery('age', 'DESC', 'M', $show_years); |
||
4096 | } |
||
4097 | |||
4098 | /** |
||
4099 | * General query on marriage ages. |
||
4100 | * |
||
4101 | * @param string[] $params |
||
4102 | * |
||
4103 | * @return string |
||
4104 | */ |
||
4105 | public function statsMarrAge($params = []) { |
||
4106 | return $this->statsMarrAgeQuery(true, 'BOTH', -1, -1, $params); |
||
4107 | } |
||
4108 | |||
4109 | /** |
||
4110 | * Find the age between husband and wife. |
||
4111 | * |
||
4112 | * @param string[] $params |
||
4113 | * |
||
4114 | * @return string |
||
4115 | */ |
||
4116 | public function ageBetweenSpousesMF($params = []) { |
||
4117 | return $this->ageBetweenSpousesQuery('nolist', 'DESC', $params); |
||
4118 | } |
||
4119 | |||
4120 | /** |
||
4121 | * Find the age between husband and wife. |
||
4122 | * |
||
4123 | * @param string[] $params |
||
4124 | * |
||
4125 | * @return string |
||
4126 | */ |
||
4127 | public function ageBetweenSpousesMFList($params = []) { |
||
4128 | return $this->ageBetweenSpousesQuery('list', 'DESC', $params); |
||
4129 | } |
||
4130 | |||
4131 | /** |
||
4132 | * Find the age between wife and husband.. |
||
4133 | * |
||
4134 | * @param string[] $params |
||
4135 | * |
||
4136 | * @return string |
||
4137 | */ |
||
4138 | public function ageBetweenSpousesFM($params = []) { |
||
4139 | return $this->ageBetweenSpousesQuery('nolist', 'ASC', $params); |
||
4140 | } |
||
4141 | |||
4142 | /** |
||
4143 | * Find the age between wife and husband.. |
||
4144 | * |
||
4145 | * @param string[] $params |
||
4146 | * |
||
4147 | * @return string |
||
4148 | */ |
||
4149 | public function ageBetweenSpousesFMList($params = []) { |
||
4150 | return $this->ageBetweenSpousesQuery('list', 'ASC', $params); |
||
4151 | } |
||
4152 | |||
4153 | /** |
||
4154 | * General query on marriage ages. |
||
4155 | * |
||
4156 | * @return string |
||
4157 | */ |
||
4158 | public function topAgeOfMarriageFamily() { |
||
4159 | return $this->ageOfMarriageQuery('name', 'DESC', ['1']); |
||
4160 | } |
||
4161 | |||
4162 | /** |
||
4163 | * General query on marriage ages. |
||
4164 | * |
||
4165 | * @return string |
||
4166 | */ |
||
4167 | public function topAgeOfMarriage() { |
||
4168 | return $this->ageOfMarriageQuery('age', 'DESC', ['1']); |
||
4169 | } |
||
4170 | |||
4171 | /** |
||
4172 | * General query on marriage ages. |
||
4173 | * |
||
4174 | * @param string[] $params |
||
4175 | * |
||
4176 | * @return string |
||
4177 | */ |
||
4178 | public function topAgeOfMarriageFamilies($params = []) { |
||
4179 | return $this->ageOfMarriageQuery('nolist', 'DESC', $params); |
||
4180 | } |
||
4181 | |||
4182 | /** |
||
4183 | * General query on marriage ages. |
||
4184 | * |
||
4185 | * @param string[] $params |
||
4186 | * |
||
4187 | * @return string |
||
4188 | */ |
||
4189 | public function topAgeOfMarriageFamiliesList($params = []) { |
||
4190 | return $this->ageOfMarriageQuery('list', 'DESC', $params); |
||
4191 | } |
||
4192 | |||
4193 | /** |
||
4194 | * General query on marriage ages. |
||
4195 | * |
||
4196 | * @return string |
||
4197 | */ |
||
4198 | public function minAgeOfMarriageFamily() { |
||
4199 | return $this->ageOfMarriageQuery('name', 'ASC', ['1']); |
||
4200 | } |
||
4201 | |||
4202 | /** |
||
4203 | * General query on marriage ages. |
||
4204 | * |
||
4205 | * @return string |
||
4206 | */ |
||
4207 | public function minAgeOfMarriage() { |
||
4208 | return $this->ageOfMarriageQuery('age', 'ASC', ['1']); |
||
4209 | } |
||
4210 | |||
4211 | /** |
||
4212 | * General query on marriage ages. |
||
4213 | * |
||
4214 | * @param string[] $params |
||
4215 | * |
||
4216 | * @return string |
||
4217 | */ |
||
4218 | public function minAgeOfMarriageFamilies($params = []) { |
||
4219 | return $this->ageOfMarriageQuery('nolist', 'ASC', $params); |
||
4220 | } |
||
4221 | |||
4222 | /** |
||
4223 | * General query on marriage ages. |
||
4224 | * |
||
4225 | * @param string[] $params |
||
4226 | * |
||
4227 | * @return string |
||
4228 | */ |
||
4229 | public function minAgeOfMarriageFamiliesList($params = []) { |
||
4230 | return $this->ageOfMarriageQuery('list', 'ASC', $params); |
||
4231 | } |
||
4232 | |||
4233 | /** |
||
4234 | * Find the youngest mother |
||
4235 | * |
||
4236 | * @return string |
||
4237 | */ |
||
4238 | public function youngestMother() { |
||
4239 | return $this->parentsQuery('full', 'ASC', 'F'); |
||
4240 | } |
||
4241 | |||
4242 | /** |
||
4243 | * Find the name of the youngest mother. |
||
4244 | * |
||
4245 | * @return string |
||
4246 | */ |
||
4247 | public function youngestMotherName() { |
||
4248 | return $this->parentsQuery('name', 'ASC', 'F'); |
||
4249 | } |
||
4250 | |||
4251 | /** |
||
4252 | * Find the age of the youngest mother. |
||
4253 | * |
||
4254 | * @param bool $show_years |
||
4255 | * |
||
4256 | * @return string |
||
4257 | */ |
||
4258 | public function youngestMotherAge($show_years = false) { |
||
4259 | return $this->parentsQuery('age', 'ASC', 'F', $show_years); |
||
4260 | } |
||
4261 | |||
4262 | /** |
||
4263 | * Find the oldest mother. |
||
4264 | * |
||
4265 | * @return string |
||
4266 | */ |
||
4267 | public function oldestMother() { |
||
4268 | return $this->parentsQuery('full', 'DESC', 'F'); |
||
4269 | } |
||
4270 | |||
4271 | /** |
||
4272 | * Find the name of the oldest mother. |
||
4273 | * |
||
4274 | * @return string |
||
4275 | */ |
||
4276 | public function oldestMotherName() { |
||
4277 | return $this->parentsQuery('name', 'DESC', 'F'); |
||
4278 | } |
||
4279 | |||
4280 | /** |
||
4281 | * Find the age of the oldest mother. |
||
4282 | * |
||
4283 | * @param bool $show_years |
||
4284 | * |
||
4285 | * @return string |
||
4286 | */ |
||
4287 | public function oldestMotherAge($show_years = false) { |
||
4288 | return $this->parentsQuery('age', 'DESC', 'F', $show_years); |
||
4289 | } |
||
4290 | |||
4291 | /** |
||
4292 | * Find the youngest father. |
||
4293 | * |
||
4294 | * @return string |
||
4295 | */ |
||
4296 | public function youngestFather() { |
||
4297 | return $this->parentsQuery('full', 'ASC', 'M'); |
||
4298 | } |
||
4299 | |||
4300 | /** |
||
4301 | * Find the name of the youngest father. |
||
4302 | * |
||
4303 | * @return string |
||
4304 | */ |
||
4305 | public function youngestFatherName() { |
||
4306 | return $this->parentsQuery('name', 'ASC', 'M'); |
||
4307 | } |
||
4308 | |||
4309 | /** |
||
4310 | * Find the age of the youngest father. |
||
4311 | * |
||
4312 | * @param bool $show_years |
||
4313 | * |
||
4314 | * @return string |
||
4315 | */ |
||
4316 | public function youngestFatherAge($show_years = false) { |
||
4317 | return $this->parentsQuery('age', 'ASC', 'M', $show_years); |
||
4318 | } |
||
4319 | |||
4320 | /** |
||
4321 | * Find the oldest father. |
||
4322 | * |
||
4323 | * @return string |
||
4324 | */ |
||
4325 | public function oldestFather() { |
||
4326 | return $this->parentsQuery('full', 'DESC', 'M'); |
||
4327 | } |
||
4328 | |||
4329 | /** |
||
4330 | * Find the name of the oldest father. |
||
4331 | * |
||
4332 | * @return string |
||
4333 | */ |
||
4334 | public function oldestFatherName() { |
||
4335 | return $this->parentsQuery('name', 'DESC', 'M'); |
||
4336 | } |
||
4337 | |||
4338 | /** |
||
4339 | * Find the age of the oldest father. |
||
4340 | * |
||
4341 | * @param bool $show_years |
||
4342 | * |
||
4343 | * @return string |
||
4344 | */ |
||
4345 | public function oldestFatherAge($show_years = false) { |
||
4346 | return $this->parentsQuery('age', 'DESC', 'M', $show_years); |
||
4347 | } |
||
4348 | |||
4349 | /** |
||
4350 | * Number of husbands. |
||
4351 | * |
||
4352 | * @return string |
||
4353 | */ |
||
4354 | public function totalMarriedMales() { |
||
4355 | $n = Database::prepare("SELECT SQL_CACHE COUNT(DISTINCT f_husb) FROM `##families` WHERE f_file=? AND f_gedcom LIKE '%\\n1 MARR%'") |
||
4356 | ->execute([$this->tree->getTreeId()]) |
||
4357 | ->fetchOne(); |
||
4358 | |||
4359 | return I18N::number($n); |
||
4360 | } |
||
4361 | |||
4362 | /** |
||
4363 | * Number of wives. |
||
4364 | * |
||
4365 | * @return string |
||
4366 | */ |
||
4367 | public function totalMarriedFemales() { |
||
4368 | $n = Database::prepare("SELECT SQL_CACHE COUNT(DISTINCT f_wife) FROM `##families` WHERE f_file=? AND f_gedcom LIKE '%\\n1 MARR%'") |
||
4369 | ->execute([$this->tree->getTreeId()]) |
||
4370 | ->fetchOne(); |
||
4371 | |||
4372 | return I18N::number($n); |
||
4373 | } |
||
4374 | |||
4375 | /** |
||
4376 | * General query on family. |
||
4377 | * |
||
4378 | * @param string $type |
||
4379 | * |
||
4380 | * @return string |
||
4381 | */ |
||
4382 | private function familyQuery($type = 'full') { |
||
4383 | $rows = $this->runSql( |
||
4384 | " SELECT SQL_CACHE f_numchil AS tot, f_id AS id" . |
||
4385 | " FROM `##families`" . |
||
4386 | " WHERE" . |
||
4387 | " f_file={$this->tree->getTreeId()}" . |
||
4388 | " AND f_numchil = (" . |
||
4389 | " SELECT max( f_numchil )" . |
||
4390 | " FROM `##families`" . |
||
4391 | " WHERE f_file ={$this->tree->getTreeId()}" . |
||
4392 | " )" . |
||
4393 | " LIMIT 1" |
||
4394 | ); |
||
4395 | if (!isset($rows[0])) { |
||
4396 | return ''; |
||
4397 | } |
||
4398 | $row = $rows[0]; |
||
4399 | $family = Family::getInstance($row['id'], $this->tree); |
||
4400 | switch ($type) { |
||
4401 | default: |
||
4402 | case 'full': |
||
4403 | if ($family->canShow()) { |
||
4404 | $result = $family->formatList(); |
||
4405 | } else { |
||
4406 | $result = I18N::translate('This information is private and cannot be shown.'); |
||
4407 | } |
||
4408 | break; |
||
4409 | case 'size': |
||
4410 | $result = I18N::number($row['tot']); |
||
4411 | break; |
||
4412 | case 'name': |
||
4413 | $result = '<a href="' . e($family->url()) . '">' . $family->getFullName() . '</a>'; |
||
4414 | break; |
||
4415 | } |
||
4416 | |||
4417 | return $result; |
||
4418 | } |
||
4419 | |||
4420 | /** |
||
4421 | * General query on families. |
||
4422 | * |
||
4423 | * @param string $type |
||
4424 | * @param string[] $params |
||
4425 | * |
||
4426 | * @return string |
||
4427 | */ |
||
4428 | private function topTenFamilyQuery($type = 'list', $params = []) { |
||
4429 | if (isset($params[0])) { |
||
4430 | $total = (int) $params[0]; |
||
4431 | } else { |
||
4432 | $total = 10; |
||
4433 | } |
||
4434 | $rows = $this->runSql( |
||
4435 | "SELECT SQL_CACHE f_numchil AS tot, f_id AS id" . |
||
4436 | " FROM `##families`" . |
||
4437 | " WHERE" . |
||
4438 | " f_file={$this->tree->getTreeId()}" . |
||
4439 | " ORDER BY tot DESC" . |
||
4440 | " LIMIT " . $total |
||
4441 | ); |
||
4442 | if (!isset($rows[0])) { |
||
4443 | return ''; |
||
4444 | } |
||
4445 | if (count($rows) < $total) { |
||
4446 | $total = count($rows); |
||
4447 | } |
||
4448 | $top10 = []; |
||
4449 | for ($c = 0; $c < $total; $c++) { |
||
4450 | $family = Family::getInstance($rows[$c]['id'], $this->tree); |
||
4451 | if ($family->canShow()) { |
||
4452 | if ($type === 'list') { |
||
4453 | $top10[] = '<li><a href="' . e($family->url()) . '">' . $family->getFullName() . '</a> - ' . I18N::plural('%s child', '%s children', $rows[$c]['tot'], I18N::number($rows[$c]['tot'])); |
||
4454 | } else { |
||
4455 | $top10[] = '<a href="' . e($family->url()) . '">' . $family->getFullName() . '</a> - ' . I18N::plural('%s child', '%s children', $rows[$c]['tot'], I18N::number($rows[$c]['tot'])); |
||
4456 | } |
||
4457 | } |
||
4458 | } |
||
4459 | if ($type === 'list') { |
||
4460 | $top10 = implode('', $top10); |
||
4461 | } else { |
||
4462 | $top10 = implode('; ', $top10); |
||
4463 | } |
||
4464 | if (I18N::direction() === 'rtl') { |
||
4465 | $top10 = str_replace(['[', ']', '(', ')', '+'], ['‏[', '‏]', '‏(', '‏)', '‏+'], $top10); |
||
4466 | } |
||
4467 | if ($type === 'list') { |
||
4468 | return '<ul>' . $top10 . '</ul>'; |
||
4469 | } |
||
4470 | |||
4471 | return $top10; |
||
4472 | } |
||
4473 | |||
4474 | /** |
||
4475 | * Find the ages between siblings. |
||
4476 | * |
||
4477 | * @param string $type |
||
4478 | * @param string[] $params |
||
4479 | * |
||
4480 | * @return string |
||
4481 | */ |
||
4482 | private function ageBetweenSiblingsQuery($type = 'list', $params = []) { |
||
4483 | if (isset($params[0])) { |
||
4484 | $total = (int) $params[0]; |
||
4485 | } else { |
||
4486 | $total = 10; |
||
4487 | } |
||
4488 | if (isset($params[1])) { |
||
4489 | $one = $params[1]; |
||
4490 | } else { |
||
4491 | $one = false; |
||
4492 | } // each family only once if true |
||
4493 | $rows = $this->runSql( |
||
4494 | " SELECT SQL_CACHE DISTINCT" . |
||
4495 | " link1.l_from AS family," . |
||
4496 | " link1.l_to AS ch1," . |
||
4497 | " link2.l_to AS ch2," . |
||
4498 | " child1.d_julianday2-child2.d_julianday2 AS age" . |
||
4499 | " FROM `##link` AS link1" . |
||
4500 | " LEFT JOIN `##dates` AS child1 ON child1.d_file = {$this->tree->getTreeId()}" . |
||
4501 | " LEFT JOIN `##dates` AS child2 ON child2.d_file = {$this->tree->getTreeId()}" . |
||
4502 | " LEFT JOIN `##link` AS link2 ON link2.l_file = {$this->tree->getTreeId()}" . |
||
4503 | " WHERE" . |
||
4504 | " link1.l_file = {$this->tree->getTreeId()} AND" . |
||
4505 | " link1.l_from = link2.l_from AND" . |
||
4506 | " link1.l_type = 'CHIL' AND" . |
||
4507 | " child1.d_gid = link1.l_to AND" . |
||
4508 | " child1.d_fact = 'BIRT' AND" . |
||
4509 | " link2.l_type = 'CHIL' AND" . |
||
4510 | " child2.d_gid = link2.l_to AND" . |
||
4511 | " child2.d_fact = 'BIRT' AND" . |
||
4512 | " child1.d_julianday2 > child2.d_julianday2 AND" . |
||
4513 | " child2.d_julianday2 <> 0 AND" . |
||
4514 | " child1.d_gid <> child2.d_gid" . |
||
4515 | " ORDER BY age DESC" . |
||
4516 | " LIMIT " . $total |
||
4517 | ); |
||
4518 | if (!isset($rows[0])) { |
||
4519 | return ''; |
||
4520 | } |
||
4521 | $top10 = []; |
||
4522 | $dist = []; |
||
4523 | foreach ($rows as $fam) { |
||
4524 | $family = Family::getInstance($fam['family'], $this->tree); |
||
4525 | $child1 = Individual::getInstance($fam['ch1'], $this->tree); |
||
4526 | $child2 = Individual::getInstance($fam['ch2'], $this->tree); |
||
4527 | if ($type == 'name') { |
||
4528 | if ($child1->canShow() && $child2->canShow()) { |
||
4529 | $return = '<a href="' . e($child2->url()) . '">' . $child2->getFullName() . '</a> '; |
||
4530 | $return .= I18N::translate('and') . ' '; |
||
4531 | $return .= '<a href="' . e($child1->url()) . '">' . $child1->getFullName() . '</a>'; |
||
4532 | $return .= ' <a href="' . e($family->url()) . '">[' . I18N::translate('View this family') . ']</a>'; |
||
4533 | } else { |
||
4534 | $return = I18N::translate('This information is private and cannot be shown.'); |
||
4535 | } |
||
4536 | |||
4537 | return $return; |
||
4538 | } |
||
4539 | $age = $fam['age']; |
||
4540 | if ((int) ($age / 365.25) > 0) { |
||
4541 | $age = (int) ($age / 365.25) . 'y'; |
||
4542 | } elseif ((int) ($age / 30.4375) > 0) { |
||
4543 | $age = (int) ($age / 30.4375) . 'm'; |
||
4544 | } else { |
||
4545 | $age = $age . 'd'; |
||
4546 | } |
||
4547 | $age = FunctionsDate::getAgeAtEvent($age); |
||
4548 | if ($type == 'age') { |
||
4549 | return $age; |
||
4550 | } |
||
4551 | if ($type == 'list') { |
||
4552 | if ($one && !in_array($fam['family'], $dist)) { |
||
4553 | if ($child1->canShow() && $child2->canShow()) { |
||
4554 | $return = '<li>'; |
||
4555 | $return .= '<a href="' . e($child2->url()) . '">' . $child2->getFullName() . '</a> '; |
||
4556 | $return .= I18N::translate('and') . ' '; |
||
4557 | $return .= '<a href="' . e($child1->url()) . '">' . $child1->getFullName() . '</a>'; |
||
4558 | $return .= ' (' . $age . ')'; |
||
4559 | $return .= ' <a href="' . e($family->url()) . '">[' . I18N::translate('View this family') . ']</a>'; |
||
4560 | $return .= '</li>'; |
||
4561 | $top10[] = $return; |
||
4562 | $dist[] = $fam['family']; |
||
4563 | } |
||
4564 | } elseif (!$one && $child1->canShow() && $child2->canShow()) { |
||
4565 | $return = '<li>'; |
||
4566 | $return .= '<a href="' . e($child2->url()) . '">' . $child2->getFullName() . '</a> '; |
||
4567 | $return .= I18N::translate('and') . ' '; |
||
4568 | $return .= '<a href="' . e($child1->url()) . '">' . $child1->getFullName() . '</a>'; |
||
4569 | $return .= ' (' . $age . ')'; |
||
4570 | $return .= ' <a href="' . e($family->url()) . '">[' . I18N::translate('View this family') . ']</a>'; |
||
4571 | $return .= '</li>'; |
||
4572 | $top10[] = $return; |
||
4573 | } |
||
4574 | } else { |
||
4575 | if ($child1->canShow() && $child2->canShow()) { |
||
4576 | $return = $child2->formatList(); |
||
4577 | $return .= '<br>' . I18N::translate('and') . '<br>'; |
||
4578 | $return .= $child1->formatList(); |
||
4579 | $return .= '<br><a href="' . e($family->url()) . '">[' . I18N::translate('View this family') . ']</a>'; |
||
4580 | |||
4581 | return $return; |
||
4582 | } else { |
||
4583 | return I18N::translate('This information is private and cannot be shown.'); |
||
4584 | } |
||
4585 | } |
||
4586 | } |
||
4587 | if ($type === 'list') { |
||
4588 | $top10 = implode('', $top10); |
||
4589 | } |
||
4590 | if (I18N::direction() === 'rtl') { |
||
4591 | $top10 = str_replace(['[', ']', '(', ')', '+'], ['‏[', '‏]', '‏(', '‏)', '‏+'], $top10); |
||
4592 | } |
||
4593 | if ($type === 'list') { |
||
4594 | return '<ul>' . $top10 . '</ul>'; |
||
4595 | } |
||
4596 | |||
4597 | return $top10; |
||
4598 | } |
||
4599 | |||
4600 | /** |
||
4601 | * Find the month in the year of the birth of the first child. |
||
4602 | * |
||
4603 | * @param bool $simple |
||
4604 | * @param bool $sex |
||
4605 | * @param int $year1 |
||
4606 | * @param int $year2 |
||
4607 | * @param string[] $params |
||
4608 | * |
||
4609 | * @return string|string[][] |
||
4610 | */ |
||
4611 | public function monthFirstChildQuery($simple = true, $sex = false, $year1 = -1, $year2 = -1, $params = []) { |
||
4612 | $WT_STATS_CHART_COLOR1 = Theme::theme()->parameter('distribution-chart-no-values'); |
||
4613 | $WT_STATS_CHART_COLOR2 = Theme::theme()->parameter('distribution-chart-high-values'); |
||
4614 | $WT_STATS_S_CHART_X = Theme::theme()->parameter('stats-small-chart-x'); |
||
4615 | $WT_STATS_S_CHART_Y = Theme::theme()->parameter('stats-small-chart-y'); |
||
4616 | |||
4617 | if ($year1 >= 0 && $year2 >= 0) { |
||
4618 | $sql_years = " AND (d_year BETWEEN '{$year1}' AND '{$year2}')"; |
||
4619 | } else { |
||
4620 | $sql_years = ''; |
||
4621 | } |
||
4622 | if ($sex) { |
||
4623 | $sql_sex1 = ', i_sex'; |
||
4624 | $sql_sex2 = " JOIN `##individuals` AS child ON child1.d_file = i_file AND child1.d_gid = child.i_id "; |
||
4625 | } else { |
||
4626 | $sql_sex1 = ''; |
||
4627 | $sql_sex2 = ''; |
||
4628 | } |
||
4629 | $sql = |
||
4630 | "SELECT SQL_CACHE d_month{$sql_sex1}, COUNT(*) AS total " . |
||
4631 | "FROM (" . |
||
4632 | " SELECT family{$sql_sex1}, MIN(date) AS d_date, d_month" . |
||
4633 | " FROM (" . |
||
4634 | " SELECT" . |
||
4635 | " link1.l_from AS family," . |
||
4636 | " link1.l_to AS child," . |
||
4637 | " child1.d_julianday2 AS date," . |
||
4638 | " child1.d_month as d_month" . |
||
4639 | $sql_sex1 . |
||
4640 | " FROM `##link` AS link1" . |
||
4641 | " LEFT JOIN `##dates` AS child1 ON child1.d_file = {$this->tree->getTreeId()}" . |
||
4642 | $sql_sex2 . |
||
4643 | " WHERE" . |
||
4644 | " link1.l_file = {$this->tree->getTreeId()} AND" . |
||
4645 | " link1.l_type = 'CHIL' AND" . |
||
4646 | " child1.d_gid = link1.l_to AND" . |
||
4647 | " child1.d_fact = 'BIRT' AND" . |
||
4648 | " child1.d_month IN ('JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC')" . |
||
4649 | $sql_years . |
||
4650 | " ORDER BY date" . |
||
4651 | " ) AS children" . |
||
4652 | " GROUP BY family, d_month{$sql_sex1}" . |
||
4653 | ") AS first_child " . |
||
4654 | "GROUP BY d_month"; |
||
4655 | if ($sex) { |
||
4656 | $sql .= ', i_sex'; |
||
4657 | } |
||
4658 | $rows = $this->runSql($sql); |
||
4659 | if ($simple) { |
||
4660 | if (isset($params[0]) && $params[0] != '') { |
||
4661 | $size = strtolower($params[0]); |
||
4662 | } else { |
||
4663 | $size = $WT_STATS_S_CHART_X . 'x' . $WT_STATS_S_CHART_Y; |
||
4664 | } |
||
4665 | if (isset($params[1]) && $params[1] != '') { |
||
4666 | $color_from = strtolower($params[1]); |
||
4667 | } else { |
||
4668 | $color_from = $WT_STATS_CHART_COLOR1; |
||
4669 | } |
||
4670 | if (isset($params[2]) && $params[2] != '') { |
||
4671 | $color_to = strtolower($params[2]); |
||
4672 | } else { |
||
4673 | $color_to = $WT_STATS_CHART_COLOR2; |
||
4674 | } |
||
4675 | $sizes = explode('x', $size); |
||
4676 | $tot = 0; |
||
4677 | foreach ($rows as $values) { |
||
4678 | $tot += $values['total']; |
||
4679 | } |
||
4680 | // Beware divide by zero |
||
4681 | if ($tot == 0) { |
||
4682 | return ''; |
||
4683 | } |
||
4684 | $text = ''; |
||
4685 | $counts = []; |
||
4686 | foreach ($rows as $values) { |
||
4687 | $counts[] = round(100 * $values['total'] / $tot, 0); |
||
4688 | switch ($values['d_month']) { |
||
4689 | default: |
||
4690 | case 'JAN': |
||
4691 | $values['d_month'] = 1; |
||
4692 | break; |
||
4693 | case 'FEB': |
||
4694 | $values['d_month'] = 2; |
||
4695 | break; |
||
4696 | case 'MAR': |
||
4697 | $values['d_month'] = 3; |
||
4698 | break; |
||
4699 | case 'APR': |
||
4700 | $values['d_month'] = 4; |
||
4701 | break; |
||
4702 | case 'MAY': |
||
4703 | $values['d_month'] = 5; |
||
4704 | break; |
||
4705 | case 'JUN': |
||
4706 | $values['d_month'] = 6; |
||
4707 | break; |
||
4708 | case 'JUL': |
||
4709 | $values['d_month'] = 7; |
||
4710 | break; |
||
4711 | case 'AUG': |
||
4712 | $values['d_month'] = 8; |
||
4713 | break; |
||
4714 | case 'SEP': |
||
4715 | $values['d_month'] = 9; |
||
4716 | break; |
||
4717 | case 'OCT': |
||
4718 | $values['d_month'] = 10; |
||
4719 | break; |
||
4720 | case 'NOV': |
||
4721 | $values['d_month'] = 11; |
||
4722 | break; |
||
4723 | case 'DEC': |
||
4724 | $values['d_month'] = 12; |
||
4725 | break; |
||
4726 | } |
||
4727 | $text .= I18N::translate(ucfirst(strtolower(($values['d_month'])))) . ' - ' . $values['total'] . '|'; |
||
4728 | } |
||
4729 | $chd = $this->arrayToExtendedEncoding($counts); |
||
4730 | $chl = substr($text, 0, -1); |
||
4731 | |||
4732 | return '<img src="https://chart.googleapis.com/chart?cht=p3&chd=e:' . $chd . '&chs=' . $size . '&chco=' . $color_from . ',' . $color_to . '&chf=bg,s,ffffff00&chl=' . $chl . '" width="' . $sizes[0] . '" height="' . $sizes[1] . '" alt="' . I18N::translate('Month of birth of first child in a relation') . '" title="' . I18N::translate('Month of birth of first child in a relation') . '" />'; |
||
4733 | } |
||
4734 | |||
4735 | return $rows; |
||
4736 | } |
||
4737 | |||
4738 | /** |
||
4739 | * Find the family with the most children. |
||
4740 | * |
||
4741 | * @return string |
||
4742 | */ |
||
4743 | public function largestFamily() { |
||
4744 | return $this->familyQuery('full'); |
||
4745 | } |
||
4746 | |||
4747 | /** |
||
4748 | * Find the number of children in the largest family. |
||
4749 | * |
||
4750 | * @return string |
||
4751 | */ |
||
4752 | public function largestFamilySize() { |
||
4753 | return $this->familyQuery('size'); |
||
4754 | } |
||
4755 | |||
4756 | /** |
||
4757 | * Find the family with the most children. |
||
4758 | * |
||
4759 | * @return string |
||
4760 | */ |
||
4761 | public function largestFamilyName() { |
||
4762 | return $this->familyQuery('name'); |
||
4763 | } |
||
4764 | |||
4765 | /** |
||
4766 | * The the families with the most children. |
||
4767 | * |
||
4768 | * @param string[] $params |
||
4769 | * |
||
4770 | * @return string |
||
4771 | */ |
||
4772 | public function topTenLargestFamily($params = []) { |
||
4773 | return $this->topTenFamilyQuery('nolist', $params); |
||
4774 | } |
||
4775 | |||
4776 | /** |
||
4777 | * Find the families with the most children. |
||
4778 | * |
||
4779 | * @param string[] $params |
||
4780 | * |
||
4781 | * @return string |
||
4782 | */ |
||
4783 | public function topTenLargestFamilyList($params = []) { |
||
4784 | return $this->topTenFamilyQuery('list', $params); |
||
4785 | } |
||
4786 | |||
4787 | /** |
||
4788 | * Create a chart of the largest families. |
||
4789 | * |
||
4790 | * @param string[] $params |
||
4791 | * |
||
4792 | * @return string |
||
4793 | */ |
||
4794 | public function chartLargestFamilies($params = []) { |
||
4795 | $WT_STATS_CHART_COLOR1 = Theme::theme()->parameter('distribution-chart-no-values'); |
||
4796 | $WT_STATS_CHART_COLOR2 = Theme::theme()->parameter('distribution-chart-high-values'); |
||
4797 | $WT_STATS_L_CHART_X = Theme::theme()->parameter('stats-large-chart-x'); |
||
4798 | $WT_STATS_S_CHART_Y = Theme::theme()->parameter('stats-small-chart-y'); |
||
4799 | |||
4800 | if (isset($params[0]) && $params[0] != '') { |
||
4801 | $size = strtolower($params[0]); |
||
4802 | } else { |
||
4803 | $size = $WT_STATS_L_CHART_X . 'x' . $WT_STATS_S_CHART_Y; |
||
4804 | } |
||
4805 | if (isset($params[1]) && $params[1] != '') { |
||
4806 | $color_from = strtolower($params[1]); |
||
4807 | } else { |
||
4808 | $color_from = $WT_STATS_CHART_COLOR1; |
||
4809 | } |
||
4810 | if (isset($params[2]) && $params[2] != '') { |
||
4811 | $color_to = strtolower($params[2]); |
||
4812 | } else { |
||
4813 | $color_to = $WT_STATS_CHART_COLOR2; |
||
4814 | } |
||
4815 | if (isset($params[3]) && $params[3] != '') { |
||
4816 | $total = strtolower($params[3]); |
||
4817 | } else { |
||
4818 | $total = 10; |
||
4819 | } |
||
4820 | $sizes = explode('x', $size); |
||
4821 | $total = (int) $total; |
||
4822 | $rows = $this->runSql( |
||
4823 | " SELECT SQL_CACHE f_numchil AS tot, f_id AS id" . |
||
4824 | " FROM `##families`" . |
||
4825 | " WHERE f_file={$this->tree->getTreeId()}" . |
||
4826 | " ORDER BY tot DESC" . |
||
4827 | " LIMIT " . $total |
||
4828 | ); |
||
4829 | if (!isset($rows[0])) { |
||
4830 | return ''; |
||
4831 | } |
||
4832 | $tot = 0; |
||
4833 | foreach ($rows as $row) { |
||
4834 | $tot += (int) $row['tot']; |
||
4835 | } |
||
4836 | $chd = ''; |
||
4837 | $chl = []; |
||
4838 | foreach ($rows as $row) { |
||
4839 | $family = Family::getInstance($row['id'], $this->tree); |
||
4840 | if ($family->canShow()) { |
||
4841 | if ($tot == 0) { |
||
4842 | $per = 0; |
||
4843 | } else { |
||
4844 | $per = round(100 * $row['tot'] / $tot, 0); |
||
4845 | } |
||
4846 | $chd .= $this->arrayToExtendedEncoding([$per]); |
||
4847 | $chl[] = htmlspecialchars_decode(strip_tags($family->getFullName())) . ' - ' . I18N::number($row['tot']); |
||
4848 | } |
||
4849 | } |
||
4850 | $chl = rawurlencode(implode('|', $chl)); |
||
4851 | |||
4852 | return "<img src=\"https://chart.googleapis.com/chart?cht=p3&chd=e:{$chd}&chs={$size}&chco={$color_from},{$color_to}&chf=bg,s,ffffff00&chl={$chl}\" width=\"{$sizes[0]}\" height=\"{$sizes[1]}\" alt=\"" . I18N::translate('Largest families') . '" title="' . I18N::translate('Largest families') . '" />'; |
||
4853 | } |
||
4854 | |||
4855 | /** |
||
4856 | * Count the total children. |
||
4857 | * |
||
4858 | * @return string |
||
4859 | */ |
||
4860 | public function totalChildren() { |
||
4864 | } |
||
4865 | |||
4866 | /** |
||
4867 | * Find the average number of children in families. |
||
4868 | * |
||
4869 | * @return string |
||
4870 | */ |
||
4871 | public function averageChildren() { |
||
4872 | $rows = $this->runSql("SELECT SQL_CACHE AVG(f_numchil) AS tot FROM `##families` WHERE f_file={$this->tree->getTreeId()}"); |
||
4873 | |||
4874 | return I18N::number($rows[0]['tot'], 2); |
||
4875 | } |
||
4876 | |||
4877 | /** |
||
4878 | * General query on familes/children. |
||
4879 | * |
||
4880 | * @param bool $simple |
||
4881 | * @param string $sex |
||
4882 | * @param int $year1 |
||
4883 | * @param int $year2 |
||
4884 | * @param string[] $params |
||
4885 | * |
||
4886 | * @return string|string[][] |
||
4887 | */ |
||
4888 | public function statsChildrenQuery($simple = true, $sex = 'BOTH', $year1 = -1, $year2 = -1, $params = []) { |
||
4889 | if ($simple) { |
||
4890 | if (isset($params[0]) && $params[0] != '') { |
||
4891 | $size = strtolower($params[0]); |
||
4892 | } else { |
||
4893 | $size = '220x200'; |
||
4894 | } |
||
4895 | $sizes = explode('x', $size); |
||
4896 | $max = 0; |
||
4897 | $rows = $this->runSql( |
||
4898 | " SELECT SQL_CACHE ROUND(AVG(f_numchil),2) AS num, FLOOR(d_year/100+1) AS century" . |
||
4899 | " FROM `##families`" . |
||
4900 | " JOIN `##dates` ON (d_file = f_file AND d_gid=f_id)" . |
||
4901 | " WHERE f_file = {$this->tree->getTreeId()}" . |
||
4902 | " AND d_julianday1<>0" . |
||
4903 | " AND d_fact = 'MARR'" . |
||
4904 | " AND d_type IN ('@#DGREGORIAN@', '@#DJULIAN@')" . |
||
4905 | " GROUP BY century" . |
||
4906 | " ORDER BY century"); |
||
4907 | if (empty($rows)) { |
||
4908 | return ''; |
||
4909 | } |
||
4910 | foreach ($rows as $values) { |
||
4911 | if ($max < $values['num']) { |
||
4912 | $max = $values['num']; |
||
4913 | } |
||
4914 | } |
||
4915 | $chm = ''; |
||
4916 | $chxl = '0:|'; |
||
4917 | $i = 0; |
||
4918 | $counts = []; |
||
4919 | foreach ($rows as $values) { |
||
4920 | if ($sizes[0] < 980) { |
||
4921 | $sizes[0] += 38; |
||
4922 | } |
||
4923 | $chxl .= $this->centuryName($values['century']) . '|'; |
||
4924 | if ($max <= 5) { |
||
4925 | $counts[] = round($values['num'] * 819.2 - 1, 1); |
||
4926 | } elseif ($max <= 10) { |
||
4927 | $counts[] = round($values['num'] * 409.6, 1); |
||
4928 | } else { |
||
4929 | $counts[] = round($values['num'] * 204.8, 1); |
||
4930 | } |
||
4931 | $chm .= 't' . $values['num'] . ',000000,0,' . $i . ',11,1|'; |
||
4932 | $i++; |
||
4933 | } |
||
4934 | $chd = $this->arrayToExtendedEncoding($counts); |
||
4935 | $chm = substr($chm, 0, -1); |
||
4936 | if ($max <= 5) { |
||
4937 | $chxl .= '1:||' . I18N::translate('century') . '|2:|0|1|2|3|4|5|3:||' . I18N::translate('Number of children') . '|'; |
||
4938 | } elseif ($max <= 10) { |
||
4939 | $chxl .= '1:||' . I18N::translate('century') . '|2:|0|1|2|3|4|5|6|7|8|9|10|3:||' . I18N::translate('Number of children') . '|'; |
||
4940 | } else { |
||
4941 | $chxl .= '1:||' . I18N::translate('century') . '|2:|0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|3:||' . I18N::translate('Number of children') . '|'; |
||
4942 | } |
||
4943 | |||
4944 | return "<img src=\"https://chart.googleapis.com/chart?cht=bvg&chs={$sizes[0]}x{$sizes[1]}&chf=bg,s,ffffff00|c,s,ffffff00&chm=D,FF0000,0,0,3,1|{$chm}&chd=e:{$chd}&chco=0000FF&chbh=30,3&chxt=x,x,y,y&chxl=" . rawurlencode($chxl) . "\" width=\"{$sizes[0]}\" height=\"{$sizes[1]}\" alt=\"" . I18N::translate('Average number of children per family') . '" title="' . I18N::translate('Average number of children per family') . '" />'; |
||
4945 | } else { |
||
4946 | if ($sex == 'M') { |
||
4947 | $sql = |
||
4948 | "SELECT SQL_CACHE num, COUNT(*) AS total FROM " . |
||
4949 | "(SELECT count(i_sex) AS num FROM `##link` " . |
||
4950 | "LEFT OUTER JOIN `##individuals` " . |
||
4951 | "ON l_from=i_id AND l_file=i_file AND i_sex='M' AND l_type='FAMC' " . |
||
4952 | "JOIN `##families` ON f_file=l_file AND f_id=l_to WHERE f_file={$this->tree->getTreeId()} GROUP BY l_to" . |
||
4953 | ") boys" . |
||
4954 | " GROUP BY num" . |
||
4955 | " ORDER BY num"; |
||
4956 | } elseif ($sex == 'F') { |
||
4957 | $sql = |
||
4958 | "SELECT SQL_CACHE num, COUNT(*) AS total FROM " . |
||
4959 | "(SELECT count(i_sex) AS num FROM `##link` " . |
||
4960 | "LEFT OUTER JOIN `##individuals` " . |
||
4961 | "ON l_from=i_id AND l_file=i_file AND i_sex='F' AND l_type='FAMC' " . |
||
4962 | "JOIN `##families` ON f_file=l_file AND f_id=l_to WHERE f_file={$this->tree->getTreeId()} GROUP BY l_to" . |
||
4963 | ") girls" . |
||
4964 | " GROUP BY num" . |
||
4965 | " ORDER BY num"; |
||
4966 | } else { |
||
4967 | $sql = "SELECT SQL_CACHE f_numchil, COUNT(*) AS total FROM `##families` "; |
||
4968 | if ($year1 >= 0 && $year2 >= 0) { |
||
4969 | $sql .= |
||
4970 | "AS fam LEFT JOIN `##dates` AS married ON married.d_file = {$this->tree->getTreeId()}" |
||
4971 | . " WHERE" |
||
4972 | . " married.d_gid = fam.f_id AND" |
||
4973 | . " fam.f_file = {$this->tree->getTreeId()} AND" |
||
4974 | . " married.d_fact = 'MARR' AND" |
||
4975 | . " married.d_year BETWEEN '{$year1}' AND '{$year2}'"; |
||
4976 | } else { |
||
4977 | $sql .= "WHERE f_file={$this->tree->getTreeId()}"; |
||
4978 | } |
||
4979 | $sql .= ' GROUP BY f_numchil'; |
||
4980 | } |
||
4981 | $rows = $this->runSql($sql); |
||
4982 | |||
4983 | return $rows; |
||
4984 | } |
||
4985 | } |
||
4986 | |||
4987 | /** |
||
4988 | * Genearl query on families/children. |
||
4989 | * |
||
4990 | * @param string[] $params |
||
4991 | * |
||
4992 | * @return string |
||
4993 | */ |
||
4994 | public function statsChildren($params = []) { |
||
4995 | return $this->statsChildrenQuery(true, 'BOTH', -1, -1, $params); |
||
4996 | } |
||
4997 | |||
4998 | /** |
||
4999 | * Find the names of siblings with the widest age gap. |
||
5000 | * |
||
5001 | * @param string[] $params |
||
5002 | * |
||
5003 | * @return string |
||
5004 | */ |
||
5005 | public function topAgeBetweenSiblingsName($params = []) { |
||
5006 | return $this->ageBetweenSiblingsQuery('name', $params); |
||
5007 | } |
||
5008 | |||
5009 | /** |
||
5010 | * Find the widest age gap between siblings. |
||
5011 | * |
||
5012 | * @param string[] $params |
||
5013 | * |
||
5014 | * @return string |
||
5015 | */ |
||
5016 | public function topAgeBetweenSiblings($params = []) { |
||
5017 | return $this->ageBetweenSiblingsQuery('age', $params); |
||
5018 | } |
||
5019 | |||
5020 | /** |
||
5021 | * Find the name of siblings with the widest age gap. |
||
5022 | * |
||
5023 | * @param string[] $params |
||
5024 | * |
||
5025 | * @return string |
||
5026 | */ |
||
5027 | public function topAgeBetweenSiblingsFullName($params = []) { |
||
5028 | return $this->ageBetweenSiblingsQuery('nolist', $params); |
||
5029 | } |
||
5030 | |||
5031 | /** |
||
5032 | * Find the siblings with the widest age gaps. |
||
5033 | * |
||
5034 | * @param string[] $params |
||
5035 | * |
||
5036 | * @return string |
||
5037 | */ |
||
5038 | public function topAgeBetweenSiblingsList($params = []) { |
||
5039 | return $this->ageBetweenSiblingsQuery('list', $params); |
||
5040 | } |
||
5041 | |||
5042 | /** |
||
5043 | * Find the families with no children. |
||
5044 | * |
||
5045 | * @return string |
||
5046 | */ |
||
5047 | private function noChildrenFamiliesQuery() { |
||
5048 | $rows = $this->runSql( |
||
5049 | " SELECT SQL_CACHE COUNT(*) AS tot" . |
||
5050 | " FROM `##families`" . |
||
5051 | " WHERE f_numchil = 0 AND f_file = {$this->tree->getTreeId()}"); |
||
5052 | |||
5053 | return $rows[0]['tot']; |
||
5054 | } |
||
5055 | |||
5056 | /** |
||
5057 | * Find the families with no children. |
||
5058 | * |
||
5059 | * @return string |
||
5060 | */ |
||
5061 | public function noChildrenFamilies() { |
||
5062 | return I18N::number($this->noChildrenFamiliesQuery()); |
||
5063 | } |
||
5064 | |||
5065 | /** |
||
5066 | * Find the families with no children. |
||
5067 | * |
||
5068 | * @param string[] $params |
||
5069 | * |
||
5070 | * @return string |
||
5071 | */ |
||
5072 | public function noChildrenFamiliesList($params = []) { |
||
5073 | if (isset($params[0]) && $params[0] != '') { |
||
5074 | $type = strtolower($params[0]); |
||
5075 | } else { |
||
5076 | $type = 'list'; |
||
5077 | } |
||
5078 | $rows = $this->runSql( |
||
5079 | " SELECT SQL_CACHE f_id AS family" . |
||
5080 | " FROM `##families` AS fam" . |
||
5081 | " WHERE f_numchil = 0 AND fam.f_file = {$this->tree->getTreeId()}"); |
||
5082 | if (!isset($rows[0])) { |
||
5083 | return ''; |
||
5084 | } |
||
5085 | $top10 = []; |
||
5086 | foreach ($rows as $row) { |
||
5087 | $family = Family::getInstance($row['family'], $this->tree); |
||
5088 | if ($family->canShow()) { |
||
5089 | if ($type == 'list') { |
||
5090 | $top10[] = '<li><a href="' . e($family->url()) . '">' . $family->getFullName() . '</a></li>'; |
||
5091 | } else { |
||
5092 | $top10[] = '<a href="' . e($family->url()) . '">' . $family->getFullName() . '</a>'; |
||
5093 | } |
||
5094 | } |
||
5095 | } |
||
5096 | if ($type == 'list') { |
||
5097 | $top10 = implode('', $top10); |
||
5098 | } else { |
||
5099 | $top10 = implode('; ', $top10); |
||
5100 | } |
||
5101 | if (I18N::direction() === 'rtl') { |
||
5102 | $top10 = str_replace(['[', ']', '(', ')', '+'], ['‏[', '‏]', '‏(', '‏)', '‏+'], $top10); |
||
5103 | } |
||
5104 | if ($type === 'list') { |
||
5105 | return '<ul>' . $top10 . '</ul>'; |
||
5106 | } |
||
5107 | |||
5108 | return $top10; |
||
5109 | } |
||
5110 | |||
5111 | /** |
||
5112 | * Create a chart of children with no families. |
||
5113 | * |
||
5114 | * @param string[] $params |
||
5115 | * |
||
5116 | * @return string |
||
5117 | */ |
||
5118 | public function chartNoChildrenFamilies($params = []) { |
||
5119 | if (isset($params[0]) && $params[0] != '') { |
||
5120 | $size = strtolower($params[0]); |
||
5121 | } else { |
||
5122 | $size = '220x200'; |
||
5123 | } |
||
5124 | if (isset($params[1]) && $params[1] != '') { |
||
5125 | $year1 = $params[1]; |
||
5126 | } else { |
||
5127 | $year1 = -1; |
||
5128 | } |
||
5129 | if (isset($params[2]) && $params[2] != '') { |
||
5130 | $year2 = $params[2]; |
||
5131 | } else { |
||
5132 | $year2 = -1; |
||
5133 | } |
||
5134 | $sizes = explode('x', $size); |
||
5135 | if ($year1 >= 0 && $year2 >= 0) { |
||
5136 | $years = " married.d_year BETWEEN '{$year1}' AND '{$year2}' AND"; |
||
5137 | } else { |
||
5138 | $years = ''; |
||
5139 | } |
||
5140 | $max = 0; |
||
5141 | $tot = 0; |
||
5142 | $rows = $this->runSql( |
||
5143 | "SELECT SQL_CACHE" . |
||
5144 | " COUNT(*) AS count," . |
||
5145 | " FLOOR(married.d_year/100+1) AS century" . |
||
5146 | " FROM" . |
||
5147 | " `##families` AS fam" . |
||
5148 | " JOIN" . |
||
5149 | " `##dates` AS married ON (married.d_file = fam.f_file AND married.d_gid = fam.f_id)" . |
||
5150 | " WHERE" . |
||
5151 | " f_numchil = 0 AND" . |
||
5152 | " fam.f_file = {$this->tree->getTreeId()} AND" . |
||
5153 | $years . |
||
5154 | " married.d_fact = 'MARR' AND" . |
||
5155 | " married.d_type IN ('@#DGREGORIAN@', '@#DJULIAN@')" . |
||
5156 | " GROUP BY century ORDER BY century" |
||
5157 | ); |
||
5158 | if (empty($rows)) { |
||
5159 | return ''; |
||
5160 | } |
||
5161 | foreach ($rows as $values) { |
||
5162 | if ($max < $values['count']) { |
||
5163 | $max = $values['count']; |
||
5164 | } |
||
5165 | $tot += (int) $values['count']; |
||
5166 | } |
||
5167 | $unknown = $this->noChildrenFamiliesQuery() - $tot; |
||
5168 | if ($unknown > $max) { |
||
5169 | $max = $unknown; |
||
5170 | } |
||
5171 | $chm = ''; |
||
5172 | $chxl = '0:|'; |
||
5173 | $i = 0; |
||
5174 | $counts = []; |
||
5175 | foreach ($rows as $values) { |
||
5176 | if ($sizes[0] < 980) { |
||
5177 | $sizes[0] += 38; |
||
5178 | } |
||
5179 | $chxl .= $this->centuryName($values['century']) . '|'; |
||
5180 | $counts[] = round(4095 * $values['count'] / ($max + 1)); |
||
5181 | $chm .= 't' . $values['count'] . ',000000,0,' . $i . ',11,1|'; |
||
5182 | $i++; |
||
5183 | } |
||
5184 | $counts[] = round(4095 * $unknown / ($max + 1)); |
||
5185 | $chd = $this->arrayToExtendedEncoding($counts); |
||
5186 | $chm .= 't' . $unknown . ',000000,0,' . $i . ',11,1'; |
||
5187 | $chxl .= I18N::translateContext('unknown century', 'Unknown') . '|1:||' . I18N::translate('century') . '|2:|0|'; |
||
5188 | $step = $max + 1; |
||
5189 | for ($d = (int) ($max + 1); $d > 0; $d--) { |
||
5190 | if (($max + 1) < ($d * 10 + 1) && fmod(($max + 1), $d) == 0) { |
||
5191 | $step = $d; |
||
5192 | } |
||
5193 | } |
||
5194 | if ($step == (int) ($max + 1)) { |
||
5195 | for ($d = (int) ($max); $d > 0; $d--) { |
||
5196 | if ($max < ($d * 10 + 1) && fmod($max, $d) == 0) { |
||
5197 | $step = $d; |
||
5198 | } |
||
5199 | } |
||
5200 | } |
||
5201 | for ($n = $step; $n <= ($max + 1); $n += $step) { |
||
5202 | $chxl .= $n . '|'; |
||
5203 | } |
||
5204 | $chxl .= '3:||' . I18N::translate('Total families') . '|'; |
||
5205 | |||
5206 | return "<img src=\"https://chart.googleapis.com/chart?cht=bvg&chs={$sizes[0]}x{$sizes[1]}&chf=bg,s,ffffff00|c,s,ffffff00&chm=D,FF0000,0,0:" . ($i - 1) . ",3,1|{$chm}&chd=e:{$chd}&chco=0000FF,ffffff00&chbh=30,3&chxt=x,x,y,y&chxl=" . rawurlencode($chxl) . "\" width=\"{$sizes[0]}\" height=\"{$sizes[1]}\" alt=\"" . I18N::translate('Number of families without children') . '" title="' . I18N::translate('Number of families without children') . '" />'; |
||
5207 | } |
||
5208 | |||
5209 | /** |
||
5210 | * Find the couple with the most grandchildren. |
||
5211 | * |
||
5212 | * @param string $type |
||
5213 | * @param string[] $params |
||
5214 | * |
||
5215 | * @return string |
||
5216 | */ |
||
5217 | private function topTenGrandFamilyQuery($type = 'list', $params = []) { |
||
5218 | if (isset($params[0])) { |
||
5219 | $total = (int) $params[0]; |
||
5220 | } else { |
||
5221 | $total = 10; |
||
5222 | } |
||
5223 | $rows = $this->runSql( |
||
5224 | "SELECT SQL_CACHE COUNT(*) AS tot, f_id AS id" . |
||
5225 | " FROM `##families`" . |
||
5226 | " JOIN `##link` AS children ON children.l_file = {$this->tree->getTreeId()}" . |
||
5227 | " JOIN `##link` AS mchildren ON mchildren.l_file = {$this->tree->getTreeId()}" . |
||
5228 | " JOIN `##link` AS gchildren ON gchildren.l_file = {$this->tree->getTreeId()}" . |
||
5229 | " WHERE" . |
||
5230 | " f_file={$this->tree->getTreeId()} AND" . |
||
5231 | " children.l_from=f_id AND" . |
||
5232 | " children.l_type='CHIL' AND" . |
||
5233 | " children.l_to=mchildren.l_from AND" . |
||
5234 | " mchildren.l_type='FAMS' AND" . |
||
5235 | " mchildren.l_to=gchildren.l_from AND" . |
||
5236 | " gchildren.l_type='CHIL'" . |
||
5237 | " GROUP BY id" . |
||
5238 | " ORDER BY tot DESC" . |
||
5239 | " LIMIT " . $total |
||
5240 | ); |
||
5241 | if (!isset($rows[0])) { |
||
5242 | return ''; |
||
5243 | } |
||
5244 | $top10 = []; |
||
5245 | foreach ($rows as $row) { |
||
5246 | $family = Family::getInstance($row['id'], $this->tree); |
||
5247 | if ($family->canShow()) { |
||
5248 | if ($type === 'list') { |
||
5249 | $top10[] = '<li><a href="' . e($family->url()) . '">' . $family->getFullName() . '</a> - ' . I18N::plural('%s grandchild', '%s grandchildren', $row['tot'], I18N::number($row['tot'])); |
||
5250 | } else { |
||
5251 | $top10[] = '<a href="' . e($family->url()) . '">' . $family->getFullName() . '</a> - ' . I18N::plural('%s grandchild', '%s grandchildren', $row['tot'], I18N::number($row['tot'])); |
||
5252 | } |
||
5253 | } |
||
5254 | } |
||
5255 | if ($type === 'list') { |
||
5256 | $top10 = implode('', $top10); |
||
5257 | } else { |
||
5258 | $top10 = implode('; ', $top10); |
||
5259 | } |
||
5260 | if (I18N::direction() === 'rtl') { |
||
5261 | $top10 = str_replace(['[', ']', '(', ')', '+'], ['‏[', '‏]', '‏(', '‏)', '‏+'], $top10); |
||
5262 | } |
||
5263 | if ($type === 'list') { |
||
5264 | return '<ul>' . $top10 . '</ul>'; |
||
5265 | } |
||
5266 | |||
5267 | return $top10; |
||
5268 | } |
||
5269 | |||
5270 | /** |
||
5271 | * Find the couple with the most grandchildren. |
||
5272 | * |
||
5273 | * @param string[] $params |
||
5274 | * |
||
5275 | * @return string |
||
5276 | */ |
||
5277 | public function topTenLargestGrandFamily($params = []) { |
||
5278 | return $this->topTenGrandFamilyQuery('nolist', $params); |
||
5279 | } |
||
5280 | |||
5281 | /** |
||
5282 | * Find the couple with the most grandchildren. |
||
5283 | * |
||
5284 | * @param string[] $params |
||
5285 | * |
||
5286 | * @return string |
||
5287 | */ |
||
5288 | public function topTenLargestGrandFamilyList($params = []) { |
||
5289 | return $this->topTenGrandFamilyQuery('list', $params); |
||
5290 | } |
||
5291 | |||
5292 | /** |
||
5293 | * Find common surnames. |
||
5294 | * |
||
5295 | * @param string $type |
||
5296 | * @param bool $show_tot |
||
5297 | * @param string[] $params |
||
5298 | * |
||
5299 | * @return string |
||
5300 | */ |
||
5301 | private function commonSurnamesQuery($type = 'list', $show_tot = false, $params = []) { |
||
5302 | $threshold = empty($params[0]) ? 10 : (int) $params[0]; |
||
5303 | $number_of_surnames = empty($params[1]) ? 10 : (int) $params[1]; |
||
5304 | $sorting = empty($params[2]) ? 'alpha' : $params[2]; |
||
5305 | |||
5306 | $surname_list = FunctionsDb::getTopSurnames($this->tree->getTreeId(), $threshold, $number_of_surnames); |
||
5307 | if (empty($surname_list)) { |
||
5308 | return ''; |
||
5309 | } |
||
5310 | |||
5311 | switch ($sorting) { |
||
5312 | default: |
||
5313 | case 'alpha': |
||
5314 | uksort($surname_list, '\Fisharebest\Webtrees\I18N::strcasecmp'); |
||
5315 | break; |
||
5316 | case 'count': |
||
5317 | asort($surname_list); |
||
5318 | break; |
||
5319 | case 'rcount': |
||
5320 | arsort($surname_list); |
||
5321 | break; |
||
5322 | } |
||
5323 | |||
5324 | // Note that we count/display SPFX SURN, but sort/group under just SURN |
||
5325 | $surnames = []; |
||
5326 | foreach (array_keys($surname_list) as $surname) { |
||
5327 | $surnames = array_merge($surnames, QueryName::surnames($this->tree, $surname, '', false, false)); |
||
5328 | } |
||
5329 | |||
5330 | return FunctionsPrintLists::surnameList($surnames, ($type == 'list' ? 1 : 2), $show_tot, 'indilist.php', $this->tree); |
||
5331 | } |
||
5332 | |||
5333 | /** |
||
5334 | * Find common surnames. |
||
5335 | * |
||
5336 | * @return string |
||
5337 | */ |
||
5338 | public function getCommonSurname() { |
||
5339 | $surnames = array_keys(FunctionsDb::getTopSurnames($this->tree->getTreeId(), 1, 1)); |
||
5340 | |||
5341 | return array_shift($surnames); |
||
5342 | } |
||
5343 | |||
5344 | /** |
||
5345 | * Find common surnames. |
||
5346 | * |
||
5347 | * @param string[] $params |
||
5348 | * |
||
5349 | * @return string |
||
5350 | */ |
||
5351 | public function commonSurnames($params = ['', '', 'alpha']) { |
||
5352 | return $this->commonSurnamesQuery('nolist', false, $params); |
||
5353 | } |
||
5354 | |||
5355 | /** |
||
5356 | * Find common surnames. |
||
5357 | * |
||
5358 | * @param string[] $params |
||
5359 | * |
||
5360 | * @return string |
||
5361 | */ |
||
5362 | public function commonSurnamesTotals($params = ['', '', 'rcount']) { |
||
5363 | return $this->commonSurnamesQuery('nolist', true, $params); |
||
5364 | } |
||
5365 | |||
5366 | /** |
||
5367 | * Find common surnames. |
||
5368 | * |
||
5369 | * @param string[] $params |
||
5370 | * |
||
5371 | * @return string |
||
5372 | */ |
||
5373 | public function commonSurnamesList($params = ['', '', 'alpha']) { |
||
5374 | return $this->commonSurnamesQuery('list', false, $params); |
||
5375 | } |
||
5376 | |||
5377 | /** |
||
5378 | * Find common surnames. |
||
5379 | * |
||
5380 | * @param string[] $params |
||
5381 | * |
||
5382 | * @return string |
||
5383 | */ |
||
5384 | public function commonSurnamesListTotals($params = ['', '', 'rcount']) { |
||
5385 | return $this->commonSurnamesQuery('list', true, $params); |
||
5386 | } |
||
5387 | |||
5388 | /** |
||
5389 | * Create a chart of common surnames. |
||
5390 | * |
||
5391 | * @param string[] $params |
||
5392 | * |
||
5393 | * @return string |
||
5394 | */ |
||
5395 | public function chartCommonSurnames($params = []) { |
||
5396 | $WT_STATS_CHART_COLOR1 = Theme::theme()->parameter('distribution-chart-no-values'); |
||
5397 | $WT_STATS_CHART_COLOR2 = Theme::theme()->parameter('distribution-chart-high-values'); |
||
5398 | $WT_STATS_S_CHART_X = Theme::theme()->parameter('stats-small-chart-x'); |
||
5399 | $WT_STATS_S_CHART_Y = Theme::theme()->parameter('stats-small-chart-y'); |
||
5400 | |||
5401 | $size = empty($params[0]) ? $WT_STATS_S_CHART_X . 'x' . $WT_STATS_S_CHART_Y : strtolower($params[0]); |
||
5402 | $color_from = empty($params[1]) ? $WT_STATS_CHART_COLOR1 : strtolower($params[1]); |
||
5403 | $color_to = empty($params[2]) ? $WT_STATS_CHART_COLOR2 : strtolower($params[2]); |
||
5404 | $number_of_surnames = empty($params[3]) ? 10 : (int) $params[3]; |
||
5405 | |||
5406 | $sizes = explode('x', $size); |
||
5407 | $tot_indi = $this->totalIndividualsQuery(); |
||
5408 | $surnames = FunctionsDb::getTopSurnames($this->tree->getTreeId(), 0, $number_of_surnames); |
||
5409 | if (empty($surnames)) { |
||
5410 | return ''; |
||
5411 | } |
||
5412 | $SURNAME_TRADITION = $this->tree->getPreference('SURNAME_TRADITION'); |
||
5413 | $all_surnames = []; |
||
5414 | $tot = 0; |
||
5415 | foreach ($surnames as $surname => $num) { |
||
5416 | $all_surnames = array_merge($all_surnames, QueryName::surnames($this->tree, I18N::strtoupper($surname), '', false, false)); |
||
5417 | $tot += $num; |
||
5418 | } |
||
5419 | $chd = ''; |
||
5420 | $chl = []; |
||
5421 | foreach ($all_surnames as $surns) { |
||
5422 | $count_per = 0; |
||
5423 | $max_name = 0; |
||
5424 | $top_name = ''; |
||
5425 | foreach ($surns as $spfxsurn => $indis) { |
||
5426 | $per = count($indis); |
||
5427 | $count_per += $per; |
||
5428 | // select most common surname from all variants |
||
5429 | if ($per > $max_name) { |
||
5430 | $max_name = $per; |
||
5431 | $top_name = $spfxsurn; |
||
5432 | } |
||
5433 | } |
||
5434 | switch ($SURNAME_TRADITION) { |
||
5435 | case 'polish': |
||
5436 | // most common surname should be in male variant (Kowalski, not Kowalska) |
||
5437 | $top_name = preg_replace(['/ska$/', '/cka$/', '/dzka$/', '/żka$/'], ['ski', 'cki', 'dzki', 'żki'], $top_name); |
||
5438 | } |
||
5439 | $per = round(100 * $count_per / $tot_indi, 0); |
||
5440 | $chd .= $this->arrayToExtendedEncoding([$per]); |
||
5441 | $chl[] = $top_name . ' - ' . I18N::number($count_per); |
||
5442 | } |
||
5443 | $per = round(100 * ($tot_indi - $tot) / $tot_indi, 0); |
||
5444 | $chd .= $this->arrayToExtendedEncoding([$per]); |
||
5445 | $chl[] = I18N::translate('Other') . ' - ' . I18N::number($tot_indi - $tot); |
||
5446 | |||
5447 | $chart_title = implode(I18N::$list_separator, $chl); |
||
5448 | $chl = implode('|', $chl); |
||
5449 | |||
5450 | return '<img src="https://chart.googleapis.com/chart?cht=p3&chd=e:' . $chd . '&chs=' . $size . '&chco=' . $color_from . ',' . $color_to . '&chf=bg,s,ffffff00&chl=' . rawurlencode($chl) . '" width="' . $sizes[0] . '" height="' . $sizes[1] . '" alt="' . $chart_title . '" title="' . $chart_title . '" />'; |
||
5451 | } |
||
5452 | |||
5453 | /** |
||
5454 | * Find common given names. |
||
5455 | * |
||
5456 | * @param string $sex |
||
5457 | * @param string $type |
||
5458 | * @param bool $show_tot |
||
5459 | * @param string[] $params |
||
5460 | * |
||
5461 | * @return string |
||
5462 | */ |
||
5463 | private function commonGivenQuery($sex = 'B', $type = 'list', $show_tot = false, $params = []) { |
||
5464 | if (isset($params[0]) && $params[0] != '' && $params[0] >= 0) { |
||
5465 | $threshold = (int) $params[0]; |
||
5466 | } else { |
||
5467 | $threshold = 1; |
||
5468 | } |
||
5469 | if (isset($params[1]) && $params[1] != '' && $params[1] >= 0) { |
||
5470 | $maxtoshow = (int) $params[1]; |
||
5471 | } else { |
||
5472 | $maxtoshow = 10; |
||
5473 | } |
||
5474 | |||
5475 | switch ($sex) { |
||
5476 | case 'M': |
||
5477 | $sex_sql = "i_sex='M'"; |
||
5478 | break; |
||
5479 | case 'F': |
||
5480 | $sex_sql = "i_sex='F'"; |
||
5481 | break; |
||
5482 | case 'U': |
||
5483 | $sex_sql = "i_sex='U'"; |
||
5484 | break; |
||
5485 | case 'B': |
||
5486 | default: |
||
5487 | $sex_sql = "i_sex<>'U'"; |
||
5488 | break; |
||
5489 | } |
||
5490 | $ged_id = $this->tree->getTreeId(); |
||
5491 | |||
5492 | $rows = Database::prepare("SELECT SQL_CACHE n_givn, COUNT(*) AS num FROM `##name` JOIN `##individuals` ON (n_id=i_id AND n_file=i_file) WHERE n_file={$ged_id} AND n_type<>'_MARNM' AND n_givn NOT IN ('@P.N.', '') AND LENGTH(n_givn)>1 AND {$sex_sql} GROUP BY n_id, n_givn") |
||
5493 | ->fetchAll(); |
||
5494 | $nameList = []; |
||
5495 | foreach ($rows as $row) { |
||
5496 | // Split “John Thomas” into “John” and “Thomas” and count against both totals |
||
5497 | foreach (explode(' ', $row->n_givn) as $given) { |
||
5498 | // Exclude initials and particles. |
||
5499 | if (!preg_match('/^([A-Z]|[a-z]{1,3})$/', $given)) { |
||
5500 | if (array_key_exists($given, $nameList)) { |
||
5501 | $nameList[$given] += $row->num; |
||
5502 | } else { |
||
5503 | $nameList[$given] = $row->num; |
||
5504 | } |
||
5505 | } |
||
5506 | } |
||
5507 | } |
||
5508 | arsort($nameList, SORT_NUMERIC); |
||
5509 | $nameList = array_slice($nameList, 0, $maxtoshow); |
||
5510 | |||
5511 | if (count($nameList) == 0) { |
||
5512 | return ''; |
||
5513 | } |
||
5514 | if ($type == 'chart') { |
||
5515 | return $nameList; |
||
5516 | } |
||
5517 | $common = []; |
||
5518 | foreach ($nameList as $given => $total) { |
||
5519 | if ($maxtoshow !== -1) { |
||
5520 | if ($maxtoshow-- <= 0) { |
||
5521 | break; |
||
5522 | } |
||
5523 | } |
||
5524 | if ($total < $threshold) { |
||
5525 | break; |
||
5526 | } |
||
5527 | if ($show_tot) { |
||
5528 | $tot = ' (' . I18N::number($total) . ')'; |
||
5529 | } else { |
||
5530 | $tot = ''; |
||
5531 | } |
||
5532 | switch ($type) { |
||
5533 | case 'table': |
||
5534 | $common[] = '<tr><td>' . $given . '</td><td data-sort="' . $total . '">' . I18N::number($total) . '</td></tr>'; |
||
5535 | break; |
||
5536 | case 'list': |
||
5537 | $common[] = '<li><span dir="auto">' . $given . '</span>' . $tot . '</li>'; |
||
5538 | break; |
||
5539 | case 'nolist': |
||
5540 | $common[] = '<span dir="auto">' . $given . '</span>' . $tot; |
||
5541 | break; |
||
5542 | } |
||
5543 | } |
||
5544 | if ($common) { |
||
5545 | switch ($type) { |
||
5546 | case 'table': |
||
5547 | $lookup = ['M' => I18N::translate('Male'), 'F' => I18N::translate('Female'), 'U' => I18N::translateContext('unknown gender', 'Unknown'), 'B' => I18N::translate('All')]; |
||
5548 | |||
5549 | return '<table ' . Datatables::givenNameTableAttributes() . '><thead><tr><th colspan="3">' . $lookup[$sex] . '</th></tr><tr><th>' . I18N::translate('Name') . '</th><th>' . I18N::translate('Individuals') . '</th></tr></thead><tbody>' . implode('', $common) . '</tbody></table>'; |
||
5550 | case 'list': |
||
5551 | return '<ul>' . implode('', $common) . '</ul>'; |
||
5552 | case 'nolist': |
||
5553 | return implode(I18N::$list_separator, $common); |
||
5554 | default: |
||
5555 | return ''; |
||
5556 | } |
||
5557 | } else { |
||
5558 | return ''; |
||
5559 | } |
||
5560 | } |
||
5561 | |||
5562 | /** |
||
5563 | * Find common give names. |
||
5564 | * |
||
5565 | * @param string[] $params |
||
5566 | * |
||
5567 | * @return string |
||
5568 | */ |
||
5569 | public function commonGiven($params = [1, 10, 'alpha']) { |
||
5570 | return $this->commonGivenQuery('B', 'nolist', false, $params); |
||
5571 | } |
||
5572 | |||
5573 | /** |
||
5574 | * Find common give names. |
||
5575 | * |
||
5576 | * @param string[] $params |
||
5577 | * |
||
5578 | * @return string |
||
5579 | */ |
||
5580 | public function commonGivenTotals($params = [1, 10, 'rcount']) { |
||
5581 | return $this->commonGivenQuery('B', 'nolist', true, $params); |
||
5582 | } |
||
5583 | |||
5584 | /** |
||
5585 | * Find common give names. |
||
5586 | * |
||
5587 | * @param string[] $params |
||
5588 | * |
||
5589 | * @return string |
||
5590 | */ |
||
5591 | public function commonGivenList($params = [1, 10, 'alpha']) { |
||
5592 | return $this->commonGivenQuery('B', 'list', false, $params); |
||
5593 | } |
||
5594 | |||
5595 | /** |
||
5596 | * Find common give names. |
||
5597 | * |
||
5598 | * @param string[] $params |
||
5599 | * |
||
5600 | * @return string |
||
5601 | */ |
||
5602 | public function commonGivenListTotals($params = [1, 10, 'rcount']) { |
||
5603 | return $this->commonGivenQuery('B', 'list', true, $params); |
||
5604 | } |
||
5605 | |||
5606 | /** |
||
5607 | * Find common give names. |
||
5608 | * |
||
5609 | * @param string[] $params |
||
5610 | * |
||
5611 | * @return string |
||
5612 | */ |
||
5613 | public function commonGivenTable($params = [1, 10, 'rcount']) { |
||
5614 | return $this->commonGivenQuery('B', 'table', false, $params); |
||
5615 | } |
||
5616 | |||
5617 | /** |
||
5618 | * Find common give names of females. |
||
5619 | * |
||
5620 | * @param string[] $params |
||
5621 | * |
||
5622 | * @return string |
||
5623 | */ |
||
5624 | public function commonGivenFemale($params = [1, 10, 'alpha']) { |
||
5625 | return $this->commonGivenQuery('F', 'nolist', false, $params); |
||
5626 | } |
||
5627 | |||
5628 | /** |
||
5629 | * Find common give names of females. |
||
5630 | * |
||
5631 | * @param string[] $params |
||
5632 | * |
||
5633 | * @return string |
||
5634 | */ |
||
5635 | public function commonGivenFemaleTotals($params = [1, 10, 'rcount']) { |
||
5636 | return $this->commonGivenQuery('F', 'nolist', true, $params); |
||
5637 | } |
||
5638 | |||
5639 | /** |
||
5640 | * Find common give names of females. |
||
5641 | * |
||
5642 | * @param string[] $params |
||
5643 | * |
||
5644 | * @return string |
||
5645 | */ |
||
5646 | public function commonGivenFemaleList($params = [1, 10, 'alpha']) { |
||
5647 | return $this->commonGivenQuery('F', 'list', false, $params); |
||
5648 | } |
||
5649 | |||
5650 | /** |
||
5651 | * Find common give names of females. |
||
5652 | * |
||
5653 | * @param string[] $params |
||
5654 | * |
||
5655 | * @return string |
||
5656 | */ |
||
5657 | public function commonGivenFemaleListTotals($params = [1, 10, 'rcount']) { |
||
5658 | return $this->commonGivenQuery('F', 'list', true, $params); |
||
5659 | } |
||
5660 | |||
5661 | /** |
||
5662 | * Find common give names of females. |
||
5663 | * |
||
5664 | * @param string[] $params |
||
5665 | * |
||
5666 | * @return string |
||
5667 | */ |
||
5668 | public function commonGivenFemaleTable($params = [1, 10, 'rcount']) { |
||
5669 | return $this->commonGivenQuery('F', 'table', false, $params); |
||
5670 | } |
||
5671 | |||
5672 | /** |
||
5673 | * Find common give names of males. |
||
5674 | * |
||
5675 | * @param string[] $params |
||
5676 | * |
||
5677 | * @return string |
||
5678 | */ |
||
5679 | public function commonGivenMale($params = [1, 10, 'alpha']) { |
||
5680 | return $this->commonGivenQuery('M', 'nolist', false, $params); |
||
5681 | } |
||
5682 | |||
5683 | /** |
||
5684 | * Find common give names of males. |
||
5685 | * |
||
5686 | * @param string[] $params |
||
5687 | * |
||
5688 | * @return string |
||
5689 | */ |
||
5690 | public function commonGivenMaleTotals($params = [1, 10, 'rcount']) { |
||
5691 | return $this->commonGivenQuery('M', 'nolist', true, $params); |
||
5692 | } |
||
5693 | |||
5694 | /** |
||
5695 | * Find common give names of males. |
||
5696 | * |
||
5697 | * @param string[] $params |
||
5698 | * |
||
5699 | * @return string |
||
5700 | */ |
||
5701 | public function commonGivenMaleList($params = [1, 10, 'alpha']) { |
||
5702 | return $this->commonGivenQuery('M', 'list', false, $params); |
||
5703 | } |
||
5704 | |||
5705 | /** |
||
5706 | * Find common give names of males. |
||
5707 | * |
||
5708 | * @param string[] $params |
||
5709 | * |
||
5710 | * @return string |
||
5711 | */ |
||
5712 | public function commonGivenMaleListTotals($params = [1, 10, 'rcount']) { |
||
5713 | return $this->commonGivenQuery('M', 'list', true, $params); |
||
5714 | } |
||
5715 | |||
5716 | /** |
||
5717 | * Find common give names of males. |
||
5718 | * |
||
5719 | * @param string[] $params |
||
5720 | * |
||
5721 | * @return string |
||
5722 | */ |
||
5723 | public function commonGivenMaleTable($params = [1, 10, 'rcount']) { |
||
5724 | return $this->commonGivenQuery('M', 'table', false, $params); |
||
5725 | } |
||
5726 | |||
5727 | /** |
||
5728 | * Find common give names of unknown sexes. |
||
5729 | * |
||
5730 | * @param string[] $params |
||
5731 | * |
||
5732 | * @return string |
||
5733 | */ |
||
5734 | public function commonGivenUnknown($params = [1, 10, 'alpha']) { |
||
5735 | return $this->commonGivenQuery('U', 'nolist', false, $params); |
||
5736 | } |
||
5737 | |||
5738 | /** |
||
5739 | * Find common give names of unknown sexes. |
||
5740 | * |
||
5741 | * @param string[] $params |
||
5742 | * |
||
5743 | * @return string |
||
5744 | */ |
||
5745 | public function commonGivenUnknownTotals($params = [1, 10, 'rcount']) { |
||
5746 | return $this->commonGivenQuery('U', 'nolist', true, $params); |
||
5747 | } |
||
5748 | |||
5749 | /** |
||
5750 | * Find common give names of unknown sexes. |
||
5751 | * |
||
5752 | * @param string[] $params |
||
5753 | * |
||
5754 | * @return string |
||
5755 | */ |
||
5756 | public function commonGivenUnknownList($params = [1, 10, 'alpha']) { |
||
5757 | return $this->commonGivenQuery('U', 'list', false, $params); |
||
5758 | } |
||
5759 | |||
5760 | /** |
||
5761 | * Find common give names of unknown sexes. |
||
5762 | * |
||
5763 | * @param string[] $params |
||
5764 | * |
||
5765 | * @return string |
||
5766 | */ |
||
5767 | public function commonGivenUnknownListTotals($params = [1, 10, 'rcount']) { |
||
5768 | return $this->commonGivenQuery('U', 'list', true, $params); |
||
5769 | } |
||
5770 | |||
5771 | /** |
||
5772 | * Find common give names of unknown sexes. |
||
5773 | * |
||
5774 | * @param string[] $params |
||
5775 | * |
||
5776 | * @return string |
||
5777 | */ |
||
5778 | public function commonGivenUnknownTable($params = [1, 10, 'rcount']) { |
||
5779 | return $this->commonGivenQuery('U', 'table', false, $params); |
||
5780 | } |
||
5781 | |||
5782 | /** |
||
5783 | * Create a chart of common given names. |
||
5784 | * |
||
5785 | * @param string[] $params |
||
5786 | * |
||
5787 | * @return string |
||
5788 | */ |
||
5789 | public function chartCommonGiven($params = []) { |
||
5790 | $WT_STATS_CHART_COLOR1 = Theme::theme()->parameter('distribution-chart-no-values'); |
||
5791 | $WT_STATS_CHART_COLOR2 = Theme::theme()->parameter('distribution-chart-high-values'); |
||
5792 | $WT_STATS_S_CHART_X = Theme::theme()->parameter('stats-small-chart-x'); |
||
5793 | $WT_STATS_S_CHART_Y = Theme::theme()->parameter('stats-small-chart-y'); |
||
5794 | |||
5795 | if (isset($params[0]) && $params[0] != '') { |
||
5796 | $size = strtolower($params[0]); |
||
5797 | } else { |
||
5798 | $size = $WT_STATS_S_CHART_X . 'x' . $WT_STATS_S_CHART_Y; |
||
5799 | } |
||
5800 | if (isset($params[1]) && $params[1] != '') { |
||
5801 | $color_from = strtolower($params[1]); |
||
5802 | } else { |
||
5803 | $color_from = $WT_STATS_CHART_COLOR1; |
||
5804 | } |
||
5805 | if (isset($params[2]) && $params[2] != '') { |
||
5806 | $color_to = strtolower($params[2]); |
||
5807 | } else { |
||
5808 | $color_to = $WT_STATS_CHART_COLOR2; |
||
5809 | } |
||
5810 | if (isset($params[4]) && $params[4] != '') { |
||
5811 | $maxtoshow = strtolower($params[4]); |
||
5812 | } else { |
||
5813 | $maxtoshow = 7; |
||
5814 | } |
||
5815 | $sizes = explode('x', $size); |
||
5816 | $tot_indi = $this->totalIndividualsQuery(); |
||
5817 | $given = $this->commonGivenQuery('B', 'chart'); |
||
5818 | if (!is_array($given)) { |
||
5819 | return ''; |
||
5820 | } |
||
5821 | $given = array_slice($given, 0, $maxtoshow); |
||
5822 | if (count($given) <= 0) { |
||
5823 | return ''; |
||
5824 | } |
||
5825 | $tot = 0; |
||
5826 | foreach ($given as $count) { |
||
5827 | $tot += $count; |
||
5828 | } |
||
5829 | $chd = ''; |
||
5830 | $chl = []; |
||
5831 | foreach ($given as $givn => $count) { |
||
5832 | if ($tot == 0) { |
||
5833 | $per = 0; |
||
5834 | } else { |
||
5835 | $per = round(100 * $count / $tot_indi, 0); |
||
5836 | } |
||
5837 | $chd .= $this->arrayToExtendedEncoding([$per]); |
||
5838 | $chl[] = $givn . ' - ' . I18N::number($count); |
||
5839 | } |
||
5840 | $per = round(100 * ($tot_indi - $tot) / $tot_indi, 0); |
||
5841 | $chd .= $this->arrayToExtendedEncoding([$per]); |
||
5842 | $chl[] = I18N::translate('Other') . ' - ' . I18N::number($tot_indi - $tot); |
||
5843 | |||
5844 | $chart_title = implode(I18N::$list_separator, $chl); |
||
5845 | $chl = implode('|', $chl); |
||
5846 | |||
5847 | return "<img src=\"https://chart.googleapis.com/chart?cht=p3&chd=e:{$chd}&chs={$size}&chco={$color_from},{$color_to}&chf=bg,s,ffffff00&chl=" . rawurlencode($chl) . "\" width=\"{$sizes[0]}\" height=\"{$sizes[1]}\" alt=\"" . $chart_title . '" title="' . $chart_title . '" />'; |
||
5848 | } |
||
5849 | |||
5850 | /** |
||
5851 | * Who is currently logged in? |
||
5852 | * |
||
5853 | * @param string $type |
||
5854 | * |
||
5855 | * @return string |
||
5856 | */ |
||
5857 | private function usersLoggedInQuery($type = 'nolist') { |
||
5858 | $content = ''; |
||
5859 | // List active users |
||
5860 | $NumAnonymous = 0; |
||
5861 | $loggedusers = []; |
||
5862 | foreach (User::allLoggedIn() as $user) { |
||
5863 | if (Auth::isAdmin() || $user->getPreference('visibleonline')) { |
||
5864 | $loggedusers[] = $user; |
||
5865 | } else { |
||
5866 | $NumAnonymous++; |
||
5867 | } |
||
5868 | } |
||
5869 | $LoginUsers = count($loggedusers); |
||
5870 | if ($LoginUsers == 0 && $NumAnonymous == 0) { |
||
5871 | return I18N::translate('No signed-in and no anonymous users'); |
||
5872 | } |
||
5873 | if ($NumAnonymous > 0) { |
||
5874 | $content .= '<b>' . I18N::plural('%s anonymous signed-in user', '%s anonymous signed-in users', $NumAnonymous, I18N::number($NumAnonymous)) . '</b>'; |
||
5875 | } |
||
5876 | if ($LoginUsers > 0) { |
||
5877 | if ($NumAnonymous) { |
||
5878 | if ($type == 'list') { |
||
5879 | $content .= '<br><br>'; |
||
5880 | } else { |
||
5881 | $content .= ' ' . I18N::translate('and') . ' '; |
||
5882 | } |
||
5883 | } |
||
5884 | $content .= '<b>' . I18N::plural('%s signed-in user', '%s signed-in users', $LoginUsers, I18N::number($LoginUsers)) . '</b>'; |
||
5885 | if ($type == 'list') { |
||
5886 | $content .= '<ul>'; |
||
5887 | } else { |
||
5888 | $content .= ': '; |
||
5889 | } |
||
5890 | } |
||
5891 | if (Auth::check()) { |
||
5892 | foreach ($loggedusers as $user) { |
||
5893 | if ($type == 'list') { |
||
5894 | $content .= '<li>' . e($user->getRealName()) . ' - ' . e($user->getUserName()); |
||
5895 | } else { |
||
5896 | $content .= e($user->getRealName()) . ' - ' . e($user->getUserName()); |
||
5897 | } |
||
5898 | if (Auth::id() != $user->getUserId() && $user->getPreference('contactmethod') != 'none') { |
||
5899 | if ($type == 'list') { |
||
5900 | $content .= '<br>'; |
||
5901 | } |
||
5902 | $content .= FontAwesome::linkIcon('email', I18N::translate('Send a message'), ['class' => 'btn btn-link', 'href' => 'message.php?to=' . rawurlencode($user->getUserName())]); |
||
5903 | } |
||
5904 | if ($type == 'list') { |
||
5905 | $content .= '</li>'; |
||
5906 | } |
||
5907 | } |
||
5908 | } |
||
5909 | if ($type == 'list') { |
||
5910 | $content .= '</ul>'; |
||
5911 | } |
||
5912 | |||
5913 | return $content; |
||
5914 | } |
||
5915 | |||
5916 | /** |
||
5917 | * NUmber of users who are currently logged in? |
||
5918 | * |
||
5919 | * @param string $type |
||
5920 | * |
||
5921 | * @return int |
||
5922 | */ |
||
5923 | private function usersLoggedInTotalQuery($type = 'all') { |
||
5924 | $anon = 0; |
||
5925 | $visible = 0; |
||
5926 | foreach (User::allLoggedIn() as $user) { |
||
5927 | if (Auth::isAdmin() || $user->getPreference('visibleonline')) { |
||
5928 | $visible++; |
||
5929 | } else { |
||
5930 | $anon++; |
||
5931 | } |
||
5932 | } |
||
5933 | if ($type == 'anon') { |
||
5934 | return $anon; |
||
5935 | } elseif ($type == 'visible') { |
||
5936 | return $visible; |
||
5937 | } else { |
||
5938 | return $visible + $anon; |
||
5939 | } |
||
5940 | } |
||
5941 | |||
5942 | /** |
||
5943 | * Who is currently logged in? |
||
5944 | * |
||
5945 | * @return string |
||
5946 | */ |
||
5947 | public function usersLoggedIn() { |
||
5948 | return $this->usersLoggedInQuery('nolist'); |
||
5949 | } |
||
5950 | |||
5951 | /** |
||
5952 | * Who is currently logged in? |
||
5953 | * |
||
5954 | * @return string |
||
5955 | */ |
||
5956 | public function usersLoggedInList() { |
||
5957 | return $this->usersLoggedInQuery('list'); |
||
5958 | } |
||
5959 | |||
5960 | /** |
||
5961 | * Who is currently logged in? |
||
5962 | * |
||
5963 | * @return int |
||
5964 | */ |
||
5965 | public function usersLoggedInTotal() { |
||
5966 | return $this->usersLoggedInTotalQuery('all'); |
||
5967 | } |
||
5968 | |||
5969 | /** |
||
5970 | * Which visitors are currently logged in? |
||
5971 | * |
||
5972 | * @return int |
||
5973 | */ |
||
5974 | public function usersLoggedInTotalAnon() { |
||
5975 | return $this->usersLoggedInTotalQuery('anon'); |
||
5976 | } |
||
5977 | |||
5978 | /** |
||
5979 | * Which visitors are currently logged in? |
||
5980 | * |
||
5981 | * @return int |
||
5982 | */ |
||
5983 | public function usersLoggedInTotalVisible() { |
||
5984 | return $this->usersLoggedInTotalQuery('visible'); |
||
5985 | } |
||
5986 | |||
5987 | /** |
||
5988 | * Get the current user's ID. |
||
5989 | * |
||
5990 | * @return null|string |
||
5991 | */ |
||
5992 | public function userId() { |
||
5993 | return Auth::id(); |
||
5994 | } |
||
5995 | |||
5996 | /** |
||
5997 | * Get the current user's username. |
||
5998 | * |
||
5999 | * @param string[] $params |
||
6000 | * |
||
6001 | * @return string |
||
6002 | */ |
||
6003 | public function userName($params = []) { |
||
6004 | if (Auth::check()) { |
||
6005 | return e(Auth::user()->getUserName()); |
||
6006 | } elseif (isset($params[0]) && $params[0] != '') { |
||
6007 | // if #username:visitor# was specified, then "visitor" will be returned when the user is not logged in |
||
6008 | return e($params[0]); |
||
6009 | } else { |
||
6010 | return ''; |
||
6011 | } |
||
6012 | } |
||
6013 | |||
6014 | /** |
||
6015 | * Get the current user's full name. |
||
6016 | * |
||
6017 | * @return string |
||
6018 | */ |
||
6019 | public function userFullName() { |
||
6020 | return Auth::check() ? Auth::user()->getRealNameHtml() : ''; |
||
6021 | } |
||
6022 | |||
6023 | /** |
||
6024 | * Get the newest registered user. |
||
6025 | * |
||
6026 | * @param string $type |
||
6027 | * @param string[] $params |
||
6028 | * |
||
6029 | * @return string |
||
6030 | */ |
||
6031 | private function getLatestUserData($type = 'userid', $params = []) { |
||
6032 | static $user_id = null; |
||
6033 | |||
6034 | if ($user_id === null) { |
||
6035 | $user = User::findLatestToRegister(); |
||
6036 | } else { |
||
6037 | $user = User::find($user_id); |
||
6038 | } |
||
6039 | |||
6040 | switch ($type) { |
||
6041 | default: |
||
6042 | case 'userid': |
||
6043 | return $user->getUserId(); |
||
6044 | case 'username': |
||
6045 | return e($user->getUserName()); |
||
6046 | case 'fullname': |
||
6047 | return $user->getRealNameHtml(); |
||
6048 | case 'regdate': |
||
6049 | if (is_array($params) && isset($params[0]) && $params[0] != '') { |
||
6050 | $datestamp = $params[0]; |
||
6051 | } else { |
||
6052 | $datestamp = I18N::dateFormat(); |
||
6053 | } |
||
6054 | |||
6055 | return FunctionsDate::timestampToGedcomDate((int) $user->getPreference('reg_timestamp'))->display(false, $datestamp); |
||
6056 | case 'regtime': |
||
6057 | if (is_array($params) && isset($params[0]) && $params[0] != '') { |
||
6058 | $datestamp = $params[0]; |
||
6059 | } else { |
||
6060 | $datestamp = str_replace('%', '', I18N::timeFormat()); |
||
6061 | } |
||
6062 | |||
6063 | return date($datestamp, (int) $user->getPreference('reg_timestamp')); |
||
6064 | case 'loggedin': |
||
6065 | if (is_array($params) && isset($params[0]) && $params[0] != '') { |
||
6066 | $yes = $params[0]; |
||
6067 | } else { |
||
6068 | $yes = I18N::translate('yes'); |
||
6069 | } |
||
6070 | if (is_array($params) && isset($params[1]) && $params[1] != '') { |
||
6071 | $no = $params[1]; |
||
6072 | } else { |
||
6073 | $no = I18N::translate('no'); |
||
6074 | } |
||
6075 | |||
6076 | return Database::prepare("SELECT SQL_NO_CACHE 1 FROM `##session` WHERE user_id=? LIMIT 1")->execute([$user->getUserId()])->fetchOne() ? $yes : $no; |
||
6077 | } |
||
6078 | } |
||
6079 | |||
6080 | /** |
||
6081 | * Get the newest registered user's ID. |
||
6082 | * |
||
6083 | * @return string |
||
6084 | */ |
||
6085 | public function latestUserId() { |
||
6086 | return $this->getLatestUserData('userid'); |
||
6087 | } |
||
6088 | |||
6089 | /** |
||
6090 | * Get the newest registered user's username. |
||
6091 | * |
||
6092 | * @return string |
||
6093 | */ |
||
6094 | public function latestUserName() { |
||
6095 | return $this->getLatestUserData('username'); |
||
6096 | } |
||
6097 | |||
6098 | /** |
||
6099 | * Get the newest registered user's real name. |
||
6100 | * |
||
6101 | * @return string |
||
6102 | */ |
||
6103 | public function latestUserFullName() { |
||
6104 | return $this->getLatestUserData('fullname'); |
||
6105 | } |
||
6106 | |||
6107 | /** |
||
6108 | * Get the date of the newest user registration. |
||
6109 | * |
||
6110 | * @param string[] $params |
||
6111 | * |
||
6112 | * @return string |
||
6113 | */ |
||
6114 | public function latestUserRegDate($params = []) { |
||
6115 | return $this->getLatestUserData('regdate', $params); |
||
6116 | } |
||
6117 | |||
6118 | /** |
||
6119 | * Find the timestamp of the latest user to register. |
||
6120 | * |
||
6121 | * @param string[] $params |
||
6122 | * |
||
6123 | * @return string |
||
6124 | */ |
||
6125 | public function latestUserRegTime($params = []) { |
||
6126 | return $this->getLatestUserData('regtime', $params); |
||
6127 | } |
||
6128 | |||
6129 | /** |
||
6130 | * Find the most recent user to log in. |
||
6131 | * |
||
6132 | * @param string[] $params |
||
6133 | * |
||
6134 | * @return string |
||
6135 | */ |
||
6136 | public function latestUserLoggedin($params = []) { |
||
6137 | return $this->getLatestUserData('loggedin', $params); |
||
6138 | } |
||
6139 | |||
6140 | /** |
||
6141 | * Create a link to contact the webmaster. |
||
6142 | * |
||
6143 | * @return string |
||
6144 | */ |
||
6145 | public function contactWebmaster() { |
||
6146 | $user_id = $this->tree->getPreference('WEBMASTER_USER_ID'); |
||
6147 | $user = User::find($user_id); |
||
6148 | if ($user) { |
||
6149 | return Theme::theme()->contactLink($user); |
||
6150 | } else { |
||
6151 | return $user_id; |
||
6152 | } |
||
6153 | } |
||
6154 | |||
6155 | /** |
||
6156 | * Create a link to contact the genealogy contact. |
||
6157 | * |
||
6158 | * @return string |
||
6159 | */ |
||
6160 | public function contactGedcom() { |
||
6161 | $user_id = $this->tree->getPreference('CONTACT_USER_ID'); |
||
6162 | $user = User::find($user_id); |
||
6163 | if ($user) { |
||
6164 | return Theme::theme()->contactLink($user); |
||
6165 | } else { |
||
6166 | return $user_id; |
||
6167 | } |
||
6168 | } |
||
6169 | |||
6170 | /** |
||
6171 | * What is the current date on the server? |
||
6172 | * |
||
6173 | * @return string |
||
6174 | */ |
||
6175 | public function serverDate() { |
||
6176 | return FunctionsDate::timestampToGedcomDate(WT_TIMESTAMP)->display(); |
||
6177 | } |
||
6178 | |||
6179 | /** |
||
6180 | * What is the current time on the server (in 12 hour clock)? |
||
6181 | * |
||
6182 | * @return string |
||
6183 | */ |
||
6184 | public function serverTime() { |
||
6185 | return date('g:i a'); |
||
6186 | } |
||
6187 | |||
6188 | /** |
||
6189 | * What is the current time on the server (in 24 hour clock)? |
||
6190 | * |
||
6191 | * @return string |
||
6192 | */ |
||
6193 | public function serverTime24() { |
||
6195 | } |
||
6196 | |||
6197 | /** |
||
6198 | * What is the timezone of the server. |
||
6199 | * |
||
6200 | * @return string |
||
6201 | */ |
||
6202 | public function serverTimezone() { |
||
6203 | return date('T'); |
||
6204 | } |
||
6205 | |||
6206 | /** |
||
6207 | * What is the client's date. |
||
6208 | * |
||
6209 | * @return string |
||
6210 | */ |
||
6211 | public function browserDate() { |
||
6212 | return FunctionsDate::timestampToGedcomDate(WT_TIMESTAMP + WT_TIMESTAMP_OFFSET)->display(); |
||
6213 | } |
||
6214 | |||
6215 | /** |
||
6216 | * What is the client's timestamp. |
||
6217 | * |
||
6218 | * @return string |
||
6219 | */ |
||
6220 | public function browserTime() { |
||
6222 | } |
||
6223 | |||
6224 | /** |
||
6225 | * What is the browser's tiemzone. |
||
6226 | * |
||
6227 | * @return string |
||
6228 | */ |
||
6229 | public function browserTimezone() { |
||
6230 | return date('T', WT_TIMESTAMP + WT_TIMESTAMP_OFFSET); |
||
6231 | } |
||
6232 | |||
6233 | /** |
||
6234 | * What is the current version of webtrees. |
||
6235 | * |
||
6236 | * @return string |
||
6237 | */ |
||
6238 | public function webtreesVersion() { |
||
6239 | return WT_VERSION; |
||
6240 | } |
||
6241 | |||
6242 | /** |
||
6243 | * These functions provide access to hitcounter for use in the HTML block. |
||
6244 | * |
||
6245 | * @param string $page_name |
||
6246 | * @param string[] $params |
||
6247 | * |
||
6248 | * @return string |
||
6249 | */ |
||
6250 | private function hitCountQuery($page_name, $params) { |
||
6270 | } |
||
6271 | |||
6272 | /** |
||
6273 | * How many times has a page been viewed. |
||
6274 | * |
||
6275 | * @param string[] $params |
||
6276 | * |
||
6277 | * @return string |
||
6278 | */ |
||
6279 | public function hitCount($params = []) { |
||
6280 | return $this->hitCountQuery(null, $params); |
||
6281 | } |
||
6282 | |||
6283 | /** |
||
6284 | * How many times has a page been viewed. |
||
6285 | * |
||
6286 | * @param string[] $params |
||
6287 | * |
||
6288 | * @return string |
||
6289 | */ |
||
6290 | public function hitCountUser($params = []) { |
||
6291 | return $this->hitCountQuery('index.php', $params); |
||
6292 | } |
||
6293 | |||
6294 | /** |
||
6295 | * How many times has a page been viewed. |
||
6296 | * |
||
6297 | * @param string[] $params |
||
6298 | * |
||
6299 | * @return string |
||
6300 | */ |
||
6301 | public function hitCountIndi($params = []) { |
||
6302 | return $this->hitCountQuery('individual.php', $params); |
||
6303 | } |
||
6304 | |||
6305 | /** |
||
6306 | * How many times has a page been viewed. |
||
6307 | * |
||
6308 | * @param string[] $params |
||
6309 | * |
||
6310 | * @return string |
||
6311 | */ |
||
6312 | public function hitCountFam($params = []) { |
||
6313 | return $this->hitCountQuery('family.php', $params); |
||
6314 | } |
||
6315 | |||
6316 | /** |
||
6317 | * How many times has a page been viewed. |
||
6318 | * |
||
6319 | * @param string[] $params |
||
6320 | * |
||
6321 | * @return string |
||
6322 | */ |
||
6323 | public function hitCountSour($params = []) { |
||
6324 | return $this->hitCountQuery('source.php', $params); |
||
6325 | } |
||
6326 | |||
6327 | /** |
||
6328 | * How many times has a page been viewed. |
||
6329 | * |
||
6330 | * @param string[] $params |
||
6331 | * |
||
6332 | * @return string |
||
6333 | */ |
||
6334 | public function hitCountRepo($params = []) { |
||
6335 | return $this->hitCountQuery('repo.php', $params); |
||
6336 | } |
||
6337 | |||
6338 | /** |
||
6339 | * How many times has a page been viewed. |
||
6340 | * |
||
6341 | * @param string[] $params |
||
6342 | * |
||
6343 | * @return string |
||
6344 | */ |
||
6345 | public function hitCountNote($params = []) { |
||
6346 | return $this->hitCountQuery('note.php', $params); |
||
6347 | } |
||
6348 | |||
6349 | /** |
||
6350 | * How many times has a page been viewed. |
||
6351 | * |
||
6352 | * @param string[] $params |
||
6353 | * |
||
6354 | * @return string |
||
6355 | */ |
||
6356 | public function hitCountObje($params = []) { |
||
6357 | return $this->hitCountQuery('mediaviewer.php', $params); |
||
6358 | } |
||
6359 | |||
6360 | /** |
||
6361 | * Convert numbers to Google's custom encoding. |
||
6362 | * |
||
6363 | * @link http://bendodson.com/news/google-extended-encoding-made-easy |
||
6364 | * |
||
6365 | * @param int[] $a |
||
6366 | * |
||
6367 | * @return string |
||
6368 | */ |
||
6369 | private function arrayToExtendedEncoding($a) { |
||
6370 | $xencoding = WT_GOOGLE_CHART_ENCODING; |
||
6371 | |||
6372 | $encoding = ''; |
||
6373 | foreach ($a as $value) { |
||
6374 | if ($value < 0) { |
||
6375 | $value = 0; |
||
6376 | } |
||
6377 | $first = (int) ($value / 64); |
||
6378 | $second = $value % 64; |
||
6379 | $encoding .= $xencoding[(int) $first] . $xencoding[(int) $second]; |
||
6380 | } |
||
6381 | |||
6382 | return $encoding; |
||
6383 | } |
||
6384 | |||
6385 | /** |
||
6386 | * Run an SQL query and cache the result. |
||
6387 | * |
||
6388 | * @param string $sql |
||
6389 | * |
||
6390 | * @return string[][] |
||
6391 | */ |
||
6392 | private function runSql($sql) { |
||
6393 | static $cache = []; |
||
6394 | |||
6395 | $id = md5($sql); |
||
6396 | if (isset($cache[$id])) { |
||
6397 | return $cache[$id]; |
||
6398 | } |
||
6399 | $rows = Database::prepare($sql)->fetchAll(PDO::FETCH_ASSOC); |
||
6400 | $cache[$id] = $rows; |
||
6401 | |||
6402 | return $rows; |
||
6403 | } |
||
6404 | |||
6405 | /** |
||
6406 | * Find the favorites for the tree. |
||
6407 | * |
||
6408 | * @return string |
||
6409 | */ |
||
6410 | public function gedcomFavorites() { |
||
6411 | if (Module::getModuleByName('gedcom_favorites')) { |
||
6412 | $block = new FamilyTreeFavoritesModule(WT_MODULES_DIR . 'gedcom_favorites'); |
||
6413 | |||
6414 | return $block->getBlock(0, false); |
||
6415 | } else { |
||
6416 | return ''; |
||
6417 | } |
||
6418 | } |
||
6419 | |||
6420 | /** |
||
6421 | * Find the favorites for the user. |
||
6422 | * |
||
6423 | * @return string |
||
6424 | */ |
||
6425 | public function userFavorites() { |
||
6426 | if (Auth::check() && Module::getModuleByName('user_favorites')) { |
||
6427 | $block = new UserFavoritesModule(WT_MODULES_DIR . 'gedcom_favorites'); |
||
6428 | |||
6429 | return $block->getBlock(0, false); |
||
6430 | } else { |
||
6431 | return ''; |
||
6432 | } |
||
6433 | } |
||
6434 | |||
6435 | /** |
||
6436 | * Find the number of favorites for the tree. |
||
6437 | * |
||
6438 | * @return int |
||
6439 | */ |
||
6440 | public function totalGedcomFavorites() { |
||
6441 | if (Module::getModuleByName('gedcom_favorites')) { |
||
6442 | return count(FamilyTreeFavoritesModule::getFavorites($this->tree)); |
||
6443 | } else { |
||
6444 | return 0; |
||
6445 | } |
||
6446 | } |
||
6447 | |||
6448 | /** |
||
6449 | * Find the number of favorites for the user. |
||
6450 | * |
||
6451 | * @return int |
||
6452 | */ |
||
6453 | public function totalUserFavorites() { |
||
6454 | if (Module::getModuleByName('user_favorites')) { |
||
6455 | return count(UserFavoritesModule::getFavorites($this->tree, Auth::id())); |
||
6456 | } else { |
||
6457 | return 0; |
||
6458 | } |
||
6459 | } |
||
6460 | |||
6461 | /** |
||
6462 | * Create any of the other blocks. |
||
6463 | * |
||
6464 | * Use as #callBlock:block_name# |
||
6465 | * |
||
6466 | * @param string[] $params |
||
6467 | * |
||
6468 | * @return string |
||
6469 | */ |
||
6470 | public function callBlock($params = []) { |
||
6471 | global $ctype; |
||
6472 | |||
6473 | if (isset($params[0]) && $params[0] != '') { |
||
6474 | $block = $params[0]; |
||
6475 | } else { |
||
6476 | return ''; |
||
6477 | } |
||
6478 | $all_blocks = []; |
||
6479 | foreach (Module::getActiveBlocks($this->tree) as $name => $active_block) { |
||
6480 | if ($ctype == 'user' && $active_block->isUserBlock() || $ctype == 'gedcom' && $active_block->isGedcomBlock()) { |
||
6481 | $all_blocks[$name] = $active_block; |
||
6482 | } |
||
6483 | } |
||
6484 | if (!array_key_exists($block, $all_blocks) || $block == 'html') { |
||
6485 | return ''; |
||
6486 | } |
||
6487 | // Build the config array |
||
6488 | array_shift($params); |
||
6489 | $cfg = []; |
||
6490 | foreach ($params as $config) { |
||
6491 | $bits = explode('=', $config); |
||
6492 | if (count($bits) < 2) { |
||
6493 | continue; |
||
6494 | } |
||
6495 | $v = array_shift($bits); |
||
6496 | $cfg[$v] = implode('=', $bits); |
||
6497 | } |
||
6498 | $block = $all_blocks[$block]; |
||
6499 | $block_id = Filter::getInteger('block_id'); |
||
6500 | $content = $block->getBlock($block_id, false, $cfg); |
||
6501 | |||
6502 | return $content; |
||
6503 | } |
||
6504 | |||
6505 | /** |
||
6506 | * How many messages in the user's inbox. |
||
6507 | * |
||
6508 | * @return string |
||
6509 | */ |
||
6510 | public function totalUserMessages() { |
||
6511 | $total = (int) Database::prepare("SELECT SQL_CACHE COUNT(*) FROM `##message` WHERE user_id = ?") |
||
6512 | ->execute([Auth::id()]) |
||
6513 | ->fetchOne(); |
||
6514 | |||
6515 | return I18N::number($total); |
||
6516 | } |
||
6517 | |||
6518 | /** |
||
6519 | * How many blog entries exist for this user. |
||
6520 | * |
||
6521 | * @return string |
||
6522 | */ |
||
6523 | public function totalUserJournal() { |
||
6524 | try { |
||
6525 | $number = (int) Database::prepare("SELECT SQL_CACHE COUNT(*) FROM `##news` WHERE user_id = ?") |
||
6526 | ->execute([Auth::id()]) |
||
6527 | ->fetchOne(); |
||
6528 | } catch (PDOException $ex) { |
||
6529 | DebugBar::addThrowable($ex); |
||
6530 | |||
6531 | // The module may not be installed, so the table may not exist. |
||
6532 | $number = 0; |
||
6533 | } |
||
6534 | |||
6535 | return I18N::number($number); |
||
6536 | } |
||
6537 | |||
6538 | /** |
||
6539 | * How many news items exist for this tree. |
||
6540 | * |
||
6541 | * @return string |
||
6542 | */ |
||
6543 | public function totalGedcomNews() { |
||
6544 | try { |
||
6545 | $number = (int) Database::prepare("SELECT SQL_CACHE COUNT(*) FROM `##news` WHERE gedcom_id = ?") |
||
6546 | ->execute([$this->tree->getTreeId()]) |
||
6547 | ->fetchOne(); |
||
6548 | } catch (PDOException $ex) { |
||
6549 | DebugBar::addThrowable($ex); |
||
6550 | |||
6551 | // The module may not be installed, so the table may not exist. |
||
6552 | $number = 0; |
||
6553 | } |
||
6554 | |||
6555 | return I18N::number($number); |
||
6556 | } |
||
6557 | |||
6558 | /** |
||
6559 | * ISO3166 3 letter codes, with their 2 letter equivalent. |
||
6560 | * NOTE: this is not 1:1. ENG/SCO/WAL/NIR => GB |
||
6561 | * NOTE: this also includes champman codes and others. Should it? |
||
6562 | * |
||
6563 | * @return string[] |
||
6564 | */ |
||
6565 | public function iso3166() { |
||
6609 | ]; |
||
6610 | } |
||
6611 | |||
6612 | /** |
||
6613 | * Country codes and names |
||
6614 | * |
||
6615 | * @return string[] |
||
6616 | */ |
||
6617 | public function getAllCountries() { |
||
6618 | return [ |
||
6619 | '???' => /* I18N: Name of a country or state */ I18N::translate('Unknown'), |
||
6620 | 'ABW' => /* I18N: Name of a country or state */ I18N::translate('Aruba'), |
||
6621 | 'AFG' => /* I18N: Name of a country or state */ I18N::translate('Afghanistan'), |
||
6622 | 'AGO' => /* I18N: Name of a country or state */ I18N::translate('Angola'), |
||
6623 | 'AIA' => /* I18N: Name of a country or state */ I18N::translate('Anguilla'), |
||
6624 | 'ALA' => /* I18N: Name of a country or state */ I18N::translate('Aland Islands'), |
||
6625 | 'ALB' => /* I18N: Name of a country or state */ I18N::translate('Albania'), |
||
6626 | 'AND' => /* I18N: Name of a country or state */ I18N::translate('Andorra'), |
||
6627 | 'ARE' => /* I18N: Name of a country or state */ I18N::translate('United Arab Emirates'), |
||
6628 | 'ARG' => /* I18N: Name of a country or state */ I18N::translate('Argentina'), |
||
6629 | 'ARM' => /* I18N: Name of a country or state */ I18N::translate('Armenia'), |
||
6630 | 'ASM' => /* I18N: Name of a country or state */ I18N::translate('American Samoa'), |
||
6631 | 'ATA' => /* I18N: Name of a country or state */ I18N::translate('Antarctica'), |
||
6632 | 'ATF' => /* I18N: Name of a country or state */ I18N::translate('French Southern Territories'), |
||
6633 | 'ATG' => /* I18N: Name of a country or state */ I18N::translate('Antigua and Barbuda'), |
||
6634 | 'AUS' => /* I18N: Name of a country or state */ I18N::translate('Australia'), |
||
6635 | 'AUT' => /* I18N: Name of a country or state */ I18N::translate('Austria'), |
||
6636 | 'AZE' => /* I18N: Name of a country or state */ I18N::translate('Azerbaijan'), |
||
6637 | 'AZR' => /* I18N: Name of a country or state */ I18N::translate('Azores'), |
||
6638 | 'BDI' => /* I18N: Name of a country or state */ I18N::translate('Burundi'), |
||
6639 | 'BEL' => /* I18N: Name of a country or state */ I18N::translate('Belgium'), |
||
6640 | 'BEN' => /* I18N: Name of a country or state */ I18N::translate('Benin'), |
||
6641 | // BES => Bonaire, Sint Eustatius and Saba |
||
6642 | 'BFA' => /* I18N: Name of a country or state */ I18N::translate('Burkina Faso'), |
||
6643 | 'BGD' => /* I18N: Name of a country or state */ I18N::translate('Bangladesh'), |
||
6644 | 'BGR' => /* I18N: Name of a country or state */ I18N::translate('Bulgaria'), |
||
6645 | 'BHR' => /* I18N: Name of a country or state */ I18N::translate('Bahrain'), |
||
6646 | 'BHS' => /* I18N: Name of a country or state */ I18N::translate('Bahamas'), |
||
6647 | 'BIH' => /* I18N: Name of a country or state */ I18N::translate('Bosnia and Herzegovina'), |
||
6648 | // BLM => Saint Barthélemy |
||
6649 | 'BLR' => /* I18N: Name of a country or state */ I18N::translate('Belarus'), |
||
6650 | 'BLZ' => /* I18N: Name of a country or state */ I18N::translate('Belize'), |
||
6651 | 'BMU' => /* I18N: Name of a country or state */ I18N::translate('Bermuda'), |
||
6652 | 'BOL' => /* I18N: Name of a country or state */ I18N::translate('Bolivia'), |
||
6653 | 'BRA' => /* I18N: Name of a country or state */ I18N::translate('Brazil'), |
||
6654 | 'BRB' => /* I18N: Name of a country or state */ I18N::translate('Barbados'), |
||
6655 | 'BRN' => /* I18N: Name of a country or state */ I18N::translate('Brunei Darussalam'), |
||
6656 | 'BTN' => /* I18N: Name of a country or state */ I18N::translate('Bhutan'), |
||
6657 | 'BVT' => /* I18N: Name of a country or state */ I18N::translate('Bouvet Island'), |
||
6658 | 'BWA' => /* I18N: Name of a country or state */ I18N::translate('Botswana'), |
||
6659 | 'CAF' => /* I18N: Name of a country or state */ I18N::translate('Central African Republic'), |
||
6660 | 'CAN' => /* I18N: Name of a country or state */ I18N::translate('Canada'), |
||
6661 | 'CCK' => /* I18N: Name of a country or state */ I18N::translate('Cocos (Keeling) Islands'), |
||
6662 | 'CHE' => /* I18N: Name of a country or state */ I18N::translate('Switzerland'), |
||
6663 | 'CHL' => /* I18N: Name of a country or state */ I18N::translate('Chile'), |
||
6664 | 'CHN' => /* I18N: Name of a country or state */ I18N::translate('China'), |
||
6665 | 'CIV' => /* I18N: Name of a country or state */ I18N::translate('Cote d’Ivoire'), |
||
6666 | 'CMR' => /* I18N: Name of a country or state */ I18N::translate('Cameroon'), |
||
6667 | 'COD' => /* I18N: Name of a country or state */ I18N::translate('Democratic Republic of the Congo'), |
||
6668 | 'COG' => /* I18N: Name of a country or state */ I18N::translate('Republic of the Congo'), |
||
6669 | 'COK' => /* I18N: Name of a country or state */ I18N::translate('Cook Islands'), |
||
6670 | 'COL' => /* I18N: Name of a country or state */ I18N::translate('Colombia'), |
||
6671 | 'COM' => /* I18N: Name of a country or state */ I18N::translate('Comoros'), |
||
6672 | 'CPV' => /* I18N: Name of a country or state */ I18N::translate('Cape Verde'), |
||
6673 | 'CRI' => /* I18N: Name of a country or state */ I18N::translate('Costa Rica'), |
||
6674 | 'CUB' => /* I18N: Name of a country or state */ I18N::translate('Cuba'), |
||
6675 | // CUW => Curaçao |
||
6676 | 'CXR' => /* I18N: Name of a country or state */ I18N::translate('Christmas Island'), |
||
6677 | 'CYM' => /* I18N: Name of a country or state */ I18N::translate('Cayman Islands'), |
||
6678 | 'CYP' => /* I18N: Name of a country or state */ I18N::translate('Cyprus'), |
||
6679 | 'CZE' => /* I18N: Name of a country or state */ I18N::translate('Czech Republic'), |
||
6680 | 'DEU' => /* I18N: Name of a country or state */ I18N::translate('Germany'), |
||
6681 | 'DJI' => /* I18N: Name of a country or state */ I18N::translate('Djibouti'), |
||
6682 | 'DMA' => /* I18N: Name of a country or state */ I18N::translate('Dominica'), |
||
6683 | 'DNK' => /* I18N: Name of a country or state */ I18N::translate('Denmark'), |
||
6684 | 'DOM' => /* I18N: Name of a country or state */ I18N::translate('Dominican Republic'), |
||
6685 | 'DZA' => /* I18N: Name of a country or state */ I18N::translate('Algeria'), |
||
6686 | 'ECU' => /* I18N: Name of a country or state */ I18N::translate('Ecuador'), |
||
6687 | 'EGY' => /* I18N: Name of a country or state */ I18N::translate('Egypt'), |
||
6688 | 'ENG' => /* I18N: Name of a country or state */ I18N::translate('England'), |
||
6689 | 'ERI' => /* I18N: Name of a country or state */ I18N::translate('Eritrea'), |
||
6690 | 'ESH' => /* I18N: Name of a country or state */ I18N::translate('Western Sahara'), |
||
6691 | 'ESP' => /* I18N: Name of a country or state */ I18N::translate('Spain'), |
||
6692 | 'EST' => /* I18N: Name of a country or state */ I18N::translate('Estonia'), |
||
6693 | 'ETH' => /* I18N: Name of a country or state */ I18N::translate('Ethiopia'), |
||
6694 | 'FIN' => /* I18N: Name of a country or state */ I18N::translate('Finland'), |
||
6695 | 'FJI' => /* I18N: Name of a country or state */ I18N::translate('Fiji'), |
||
6696 | 'FLD' => /* I18N: Name of a country or state */ I18N::translate('Flanders'), |
||
6697 | 'FLK' => /* I18N: Name of a country or state */ I18N::translate('Falkland Islands'), |
||
6698 | 'FRA' => /* I18N: Name of a country or state */ I18N::translate('France'), |
||
6699 | 'FRO' => /* I18N: Name of a country or state */ I18N::translate('Faroe Islands'), |
||
6700 | 'FSM' => /* I18N: Name of a country or state */ I18N::translate('Micronesia'), |
||
6701 | 'GAB' => /* I18N: Name of a country or state */ I18N::translate('Gabon'), |
||
6702 | 'GBR' => /* I18N: Name of a country or state */ I18N::translate('United Kingdom'), |
||
6703 | 'GEO' => /* I18N: Name of a country or state */ I18N::translate('Georgia'), |
||
6704 | 'GGY' => /* I18N: Name of a country or state */ I18N::translate('Guernsey'), |
||
6705 | 'GHA' => /* I18N: Name of a country or state */ I18N::translate('Ghana'), |
||
6706 | 'GIB' => /* I18N: Name of a country or state */ I18N::translate('Gibraltar'), |
||
6707 | 'GIN' => /* I18N: Name of a country or state */ I18N::translate('Guinea'), |
||
6708 | 'GLP' => /* I18N: Name of a country or state */ I18N::translate('Guadeloupe'), |
||
6709 | 'GMB' => /* I18N: Name of a country or state */ I18N::translate('Gambia'), |
||
6710 | 'GNB' => /* I18N: Name of a country or state */ I18N::translate('Guinea-Bissau'), |
||
6711 | 'GNQ' => /* I18N: Name of a country or state */ I18N::translate('Equatorial Guinea'), |
||
6712 | 'GRC' => /* I18N: Name of a country or state */ I18N::translate('Greece'), |
||
6713 | 'GRD' => /* I18N: Name of a country or state */ I18N::translate('Grenada'), |
||
6714 | 'GRL' => /* I18N: Name of a country or state */ I18N::translate('Greenland'), |
||
6715 | 'GTM' => /* I18N: Name of a country or state */ I18N::translate('Guatemala'), |
||
6716 | 'GUF' => /* I18N: Name of a country or state */ I18N::translate('French Guiana'), |
||
6717 | 'GUM' => /* I18N: Name of a country or state */ I18N::translate('Guam'), |
||
6718 | 'GUY' => /* I18N: Name of a country or state */ I18N::translate('Guyana'), |
||
6719 | 'HKG' => /* I18N: Name of a country or state */ I18N::translate('Hong Kong'), |
||
6720 | 'HMD' => /* I18N: Name of a country or state */ I18N::translate('Heard Island and McDonald Islands'), |
||
6721 | 'HND' => /* I18N: Name of a country or state */ I18N::translate('Honduras'), |
||
6722 | 'HRV' => /* I18N: Name of a country or state */ I18N::translate('Croatia'), |
||
6723 | 'HTI' => /* I18N: Name of a country or state */ I18N::translate('Haiti'), |
||
6724 | 'HUN' => /* I18N: Name of a country or state */ I18N::translate('Hungary'), |
||
6725 | 'IDN' => /* I18N: Name of a country or state */ I18N::translate('Indonesia'), |
||
6726 | 'IND' => /* I18N: Name of a country or state */ I18N::translate('India'), |
||
6727 | 'IOM' => /* I18N: Name of a country or state */ I18N::translate('Isle of Man'), |
||
6728 | 'IOT' => /* I18N: Name of a country or state */ I18N::translate('British Indian Ocean Territory'), |
||
6729 | 'IRL' => /* I18N: Name of a country or state */ I18N::translate('Ireland'), |
||
6730 | 'IRN' => /* I18N: Name of a country or state */ I18N::translate('Iran'), |
||
6731 | 'IRQ' => /* I18N: Name of a country or state */ I18N::translate('Iraq'), |
||
6732 | 'ISL' => /* I18N: Name of a country or state */ I18N::translate('Iceland'), |
||
6733 | 'ISR' => /* I18N: Name of a country or state */ I18N::translate('Israel'), |
||
6734 | 'ITA' => /* I18N: Name of a country or state */ I18N::translate('Italy'), |
||
6735 | 'JAM' => /* I18N: Name of a country or state */ I18N::translate('Jamaica'), |
||
6736 | //'JEY' => Jersey |
||
6737 | 'JOR' => /* I18N: Name of a country or state */ I18N::translate('Jordan'), |
||
6738 | 'JPN' => /* I18N: Name of a country or state */ I18N::translate('Japan'), |
||
6739 | 'KAZ' => /* I18N: Name of a country or state */ I18N::translate('Kazakhstan'), |
||
6740 | 'KEN' => /* I18N: Name of a country or state */ I18N::translate('Kenya'), |
||
6741 | 'KGZ' => /* I18N: Name of a country or state */ I18N::translate('Kyrgyzstan'), |
||
6742 | 'KHM' => /* I18N: Name of a country or state */ I18N::translate('Cambodia'), |
||
6743 | 'KIR' => /* I18N: Name of a country or state */ I18N::translate('Kiribati'), |
||
6744 | 'KNA' => /* I18N: Name of a country or state */ I18N::translate('Saint Kitts and Nevis'), |
||
6745 | 'KOR' => /* I18N: Name of a country or state */ I18N::translate('Korea'), |
||
6746 | 'KWT' => /* I18N: Name of a country or state */ I18N::translate('Kuwait'), |
||
6747 | 'LAO' => /* I18N: Name of a country or state */ I18N::translate('Laos'), |
||
6748 | 'LBN' => /* I18N: Name of a country or state */ I18N::translate('Lebanon'), |
||
6749 | 'LBR' => /* I18N: Name of a country or state */ I18N::translate('Liberia'), |
||
6750 | 'LBY' => /* I18N: Name of a country or state */ I18N::translate('Libya'), |
||
6751 | 'LCA' => /* I18N: Name of a country or state */ I18N::translate('Saint Lucia'), |
||
6752 | 'LIE' => /* I18N: Name of a country or state */ I18N::translate('Liechtenstein'), |
||
6753 | 'LKA' => /* I18N: Name of a country or state */ I18N::translate('Sri Lanka'), |
||
6754 | 'LSO' => /* I18N: Name of a country or state */ I18N::translate('Lesotho'), |
||
6755 | 'LTU' => /* I18N: Name of a country or state */ I18N::translate('Lithuania'), |
||
6756 | 'LUX' => /* I18N: Name of a country or state */ I18N::translate('Luxembourg'), |
||
6757 | 'LVA' => /* I18N: Name of a country or state */ I18N::translate('Latvia'), |
||
6758 | 'MAC' => /* I18N: Name of a country or state */ I18N::translate('Macau'), |
||
6759 | // MAF => Saint Martin |
||
6876 | ]; |
||
6877 | } |
||
6878 | |||
6879 | /** |
||
6880 | * Century name, English => 21st, Polish => XXI, etc. |
||
6881 | * |
||
6882 | * @param int $century |
||
6883 | * |
||
6884 | * @return string |
||
6885 | */ |
||
6886 | private function centuryName($century) { |
||
6887 | if ($century < 0) { |
||
6888 | return str_replace(-$century, self::centuryName(-$century), /* I18N: BCE=Before the Common Era, for Julian years < 0. See http://en.wikipedia.org/wiki/Common_Era */ |
||
6889 | I18N::translate('%s BCE', I18N::number(-$century))); |
||
6890 | } |
||
6891 | // The current chart engine (Google charts) can't handle <sup></sup> markup |
||
6892 | switch ($century) { |
||
6893 | case 21: |
||
6894 | return strip_tags(I18N::translateContext('CENTURY', '21st')); |
||
6895 | case 20: |
||
6896 | return strip_tags(I18N::translateContext('CENTURY', '20th')); |
||
6897 | case 19: |
||
6898 | return strip_tags(I18N::translateContext('CENTURY', '19th')); |
||
6937 | } |
||
6938 | } |
||
6939 | } |
||
6940 |
PHP has two types of connecting operators (logical operators, and boolean operators):
and
&&
or
||
The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like
&&
, or||
.Let’s take a look at a few examples:
Logical Operators are used for Control-Flow
One case where you explicitly want to use logical operators is for control-flow such as this:
Since
die
introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined withthrow
at this point:These limitations lead to logical operators rarely being of use in current PHP code.