Passed
Push — main ( b8537a...489945 )
by Greg
07:15
created

FamilyTreeStatisticsModule::binaryColumn()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 4
nop 2
dl 0
loc 13
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2023 webtrees development team
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\Module;
21
22
use Fisharebest\Webtrees\Auth;
23
use Fisharebest\Webtrees\DB;
24
use Fisharebest\Webtrees\I18N;
25
use Fisharebest\Webtrees\Individual;
26
use Fisharebest\Webtrees\Registry;
27
use Fisharebest\Webtrees\Services\ModuleService;
28
use Fisharebest\Webtrees\Statistics;
29
use Fisharebest\Webtrees\Tree;
30
use Fisharebest\Webtrees\Validator;
31
use Illuminate\Database\Query\Expression;
32
use Illuminate\Support\Str;
33
use Psr\Http\Message\ServerRequestInterface;
34
35
use function array_slice;
36
use function extract;
37
use function view;
38
39
use const EXTR_OVERWRITE;
40
41
/**
42
 * Class FamilyTreeStatisticsModule
43
 */
44
class FamilyTreeStatisticsModule extends AbstractModule implements ModuleBlockInterface
45
{
46
    use ModuleBlockTrait;
47
48
    /** Show this number of surnames by default */
49
    private const DEFAULT_NUMBER_OF_SURNAMES = '10';
50
51
    private ModuleService $module_service;
52
53
    /**
54
     * @param ModuleService $module_service
55
     */
56
    public function __construct(ModuleService $module_service)
57
    {
58
        $this->module_service = $module_service;
59
    }
60
61
    /**
62
     * How should this module be identified in the control panel, etc.?
63
     *
64
     * @return string
65
     */
66
    public function title(): string
67
    {
68
        /* I18N: Name of a module */
69
        return I18N::translate('Statistics');
70
    }
71
72
    /**
73
     * A sentence describing what this module does.
74
     *
75
     * @return string
76
     */
77
    public function description(): string
78
    {
79
        /* I18N: Description of “Statistics” module */
80
        return I18N::translate('The size of the family tree, earliest and latest events, common names, etc.');
81
    }
82
83
    /**
84
     * Generate the HTML content of this block.
85
     *
86
     * @param Tree                 $tree
87
     * @param int                  $block_id
88
     * @param string               $context
89
     * @param array<string,string> $config
90
     *
91
     * @return string
92
     */
93
    public function getBlock(Tree $tree, int $block_id, string $context, array $config = []): string
94
    {
95
        $statistics = Registry::container()->get(Statistics::class);
96
97
        $show_last_update     = $this->getBlockSetting($block_id, 'show_last_update', '1');
98
        $show_common_surnames = $this->getBlockSetting($block_id, 'show_common_surnames', '1');
99
        $number_of_surnames   = (int) $this->getBlockSetting($block_id, 'number_of_surnames', self::DEFAULT_NUMBER_OF_SURNAMES);
100
        $stat_indi            = $this->getBlockSetting($block_id, 'stat_indi', '1');
101
        $stat_fam             = $this->getBlockSetting($block_id, 'stat_fam', '1');
102
        $stat_sour            = $this->getBlockSetting($block_id, 'stat_sour', '1');
103
        $stat_media           = $this->getBlockSetting($block_id, 'stat_media', '1');
104
        $stat_repo            = $this->getBlockSetting($block_id, 'stat_repo', '1');
105
        $stat_surname         = $this->getBlockSetting($block_id, 'stat_surname', '1');
106
        $stat_events          = $this->getBlockSetting($block_id, 'stat_events', '1');
107
        $stat_users           = $this->getBlockSetting($block_id, 'stat_users', '1');
108
        $stat_first_birth     = $this->getBlockSetting($block_id, 'stat_first_birth', '1');
109
        $stat_last_birth      = $this->getBlockSetting($block_id, 'stat_last_birth', '1');
110
        $stat_first_death     = $this->getBlockSetting($block_id, 'stat_first_death', '1');
111
        $stat_last_death      = $this->getBlockSetting($block_id, 'stat_last_death', '1');
112
        $stat_long_life       = $this->getBlockSetting($block_id, 'stat_long_life', '1');
113
        $stat_avg_life        = $this->getBlockSetting($block_id, 'stat_avg_life', '1');
114
        $stat_most_chil       = $this->getBlockSetting($block_id, 'stat_most_chil', '1');
115
        $stat_avg_chil        = $this->getBlockSetting($block_id, 'stat_avg_chil', '1');
116
117
        extract($config, EXTR_OVERWRITE);
118
119
        if ($show_common_surnames === '1') {
120
            $query = DB::table('name')
121
                ->where('n_file', '=', $tree->id())
122
                ->where('n_type', '<>', '_MARNM')
123
                ->where('n_surn', '<>', '')
124
                ->where('n_surn', '<>', Individual::NOMEN_NESCIO)
125
                ->select([
126
                    DB::binaryColumn('n_surn', 'n_surn'),
127
                    DB::binaryColumn('n_surname', 'n_surname'),
128
                    new Expression('COUNT(*) AS total'),
129
                ])
130
                ->groupBy([
131
                    DB::binaryColumn('n_surn'),
132
                    DB::binaryColumn('n_surname'),
133
                ]);
134
135
            /** @var array<array<int>> $top_surnames */
136
            $top_surnames = [];
137
138
            foreach ($query->get() as $row) {
139
                $row->n_surn = $row->n_surn === '' ? $row->n_surname : $row->n_surn;
140
                $row->n_surn = I18N::strtoupper(I18N::language()->normalize($row->n_surn));
141
142
                $top_surnames[$row->n_surn][$row->n_surname] ??= 0;
143
                $top_surnames[$row->n_surn][$row->n_surname] += (int) $row->total;
144
            }
145
146
            uasort($top_surnames, static fn (array $x, array $y): int => array_sum($y) <=> array_sum($x));
147
148
            $top_surnames = array_slice($top_surnames, 0, $number_of_surnames, true);
149
150
            // Find a module providing individual lists
151
            $module = $this->module_service
152
                ->findByComponent(ModuleListInterface::class, $tree, Auth::user())
153
                ->first(static fn (ModuleInterface $module): bool => $module instanceof IndividualListModule);
154
155
            $surnames = view('lists/surnames-compact-list', [
156
                'module'   => $module,
157
                'totals'   => false,
158
                'surnames' => $top_surnames,
159
                'tree'     => $tree,
160
            ]);
161
        } else {
162
            $surnames = '';
163
        }
164
165
        $content = view('modules/gedcom_stats/statistics', [
166
            'show_last_update'     => $show_last_update,
167
            'show_common_surnames' => $show_common_surnames,
168
            'number_of_surnames'   => $number_of_surnames,
169
            'stat_indi'            => $stat_indi,
170
            'stat_fam'             => $stat_fam,
171
            'stat_sour'            => $stat_sour,
172
            'stat_media'           => $stat_media,
173
            'stat_repo'            => $stat_repo,
174
            'stat_surname'         => $stat_surname,
175
            'stat_events'          => $stat_events,
176
            'stat_users'           => $stat_users,
177
            'stat_first_birth'     => $stat_first_birth,
178
            'stat_last_birth'      => $stat_last_birth,
179
            'stat_first_death'     => $stat_first_death,
180
            'stat_last_death'      => $stat_last_death,
181
            'stat_long_life'       => $stat_long_life,
182
            'stat_avg_life'        => $stat_avg_life,
183
            'stat_most_chil'       => $stat_most_chil,
184
            'stat_avg_chil'        => $stat_avg_chil,
185
            'surnames'             => $surnames,
186
        ]);
187
188
        $content = $statistics->embedTags($content);
189
190
        if ($context !== self::CONTEXT_EMBED) {
191
            return view('modules/block-template', [
192
                'block'      => Str::kebab($this->name()),
193
                'id'         => $block_id,
194
                'config_url' => $this->configUrl($tree, $context, $block_id),
195
                'title'      => $this->title(),
196
                'content'    => $content,
197
            ]);
198
        }
199
200
        return $content;
201
    }
202
203
    /**
204
     * Should this block load asynchronously using AJAX?
205
     *
206
     * Simple blocks are faster in-line, more complex ones can be loaded later.
207
     *
208
     * @return bool
209
     */
210
    public function loadAjax(): bool
211
    {
212
        return true;
213
    }
214
215
    /**
216
     * Can this block be shown on the user’s home page?
217
     *
218
     * @return bool
219
     */
220
    public function isUserBlock(): bool
221
    {
222
        return true;
223
    }
224
225
    /**
226
     * Can this block be shown on the tree’s home page?
227
     *
228
     * @return bool
229
     */
230
    public function isTreeBlock(): bool
231
    {
232
        return true;
233
    }
234
235
    /**
236
     * Update the configuration for a block.
237
     *
238
     * @param ServerRequestInterface $request
239
     * @param int     $block_id
240
     *
241
     * @return void
242
     */
243
    public function saveBlockConfiguration(ServerRequestInterface $request, int $block_id): void
244
    {
245
        $show_last_update     = Validator::parsedBody($request)->boolean('show_last_update', false);
246
        $show_common_surnames = Validator::parsedBody($request)->boolean('show_common_surnames', false);
247
        $number_of_surnames   = Validator::parsedBody($request)->integer('number_of_surnames');
248
        $stat_indi            = Validator::parsedBody($request)->boolean('stat_indi', false);
249
        $stat_fam             = Validator::parsedBody($request)->boolean('stat_fam', false);
250
        $stat_sour            = Validator::parsedBody($request)->boolean('stat_sour', false);
251
        $stat_other           = Validator::parsedBody($request)->boolean('stat_other', false);
252
        $stat_media           = Validator::parsedBody($request)->boolean('stat_media', false);
253
        $stat_repo            = Validator::parsedBody($request)->boolean('stat_repo', false);
254
        $stat_surname         = Validator::parsedBody($request)->boolean('stat_surname', false);
255
        $stat_events          = Validator::parsedBody($request)->boolean('stat_events', false);
256
        $stat_users           = Validator::parsedBody($request)->boolean('stat_users', false);
257
        $stat_first_birth     = Validator::parsedBody($request)->boolean('stat_first_birth', false);
258
        $stat_last_birth      = Validator::parsedBody($request)->boolean('stat_last_birth', false);
259
        $stat_first_death     = Validator::parsedBody($request)->boolean('stat_first_death', false);
260
        $stat_last_death      = Validator::parsedBody($request)->boolean('stat_last_death', false);
261
        $stat_long_life       = Validator::parsedBody($request)->boolean('stat_long_life', false);
262
        $stat_avg_life        = Validator::parsedBody($request)->boolean('stat_avg_life', false);
263
        $stat_most_chil       = Validator::parsedBody($request)->boolean('stat_most_chil', false);
264
        $stat_avg_chil        = Validator::parsedBody($request)->boolean('stat_avg_chil', false);
265
266
        $this->setBlockSetting($block_id, 'show_last_update', (string) $show_last_update);
267
        $this->setBlockSetting($block_id, 'show_common_surnames', (string) $show_common_surnames);
268
        $this->setBlockSetting($block_id, 'number_of_surnames', (string) $number_of_surnames);
269
        $this->setBlockSetting($block_id, 'stat_indi', (string) $stat_indi);
270
        $this->setBlockSetting($block_id, 'stat_fam', (string) $stat_fam);
271
        $this->setBlockSetting($block_id, 'stat_sour', (string) $stat_sour);
272
        $this->setBlockSetting($block_id, 'stat_other', (string) $stat_other);
273
        $this->setBlockSetting($block_id, 'stat_media', (string) $stat_media);
274
        $this->setBlockSetting($block_id, 'stat_repo', (string) $stat_repo);
275
        $this->setBlockSetting($block_id, 'stat_surname', (string) $stat_surname);
276
        $this->setBlockSetting($block_id, 'stat_events', (string) $stat_events);
277
        $this->setBlockSetting($block_id, 'stat_users', (string) $stat_users);
278
        $this->setBlockSetting($block_id, 'stat_first_birth', (string) $stat_first_birth);
279
        $this->setBlockSetting($block_id, 'stat_last_birth', (string) $stat_last_birth);
280
        $this->setBlockSetting($block_id, 'stat_first_death', (string) $stat_first_death);
281
        $this->setBlockSetting($block_id, 'stat_last_death', (string) $stat_last_death);
282
        $this->setBlockSetting($block_id, 'stat_long_life', (string) $stat_long_life);
283
        $this->setBlockSetting($block_id, 'stat_avg_life', (string) $stat_avg_life);
284
        $this->setBlockSetting($block_id, 'stat_most_chil', (string) $stat_most_chil);
285
        $this->setBlockSetting($block_id, 'stat_avg_chil', (string) $stat_avg_chil);
286
    }
287
288
    /**
289
     * An HTML form to edit block settings
290
     *
291
     * @param Tree $tree
292
     * @param int  $block_id
293
     *
294
     * @return string
295
     */
296
    public function editBlockConfiguration(Tree $tree, int $block_id): string
297
    {
298
        $show_last_update     = $this->getBlockSetting($block_id, 'show_last_update', '1');
299
        $show_common_surnames = $this->getBlockSetting($block_id, 'show_common_surnames', '1');
300
        $number_of_surnames   = $this->getBlockSetting($block_id, 'number_of_surnames', self::DEFAULT_NUMBER_OF_SURNAMES);
301
        $stat_indi            = $this->getBlockSetting($block_id, 'stat_indi', '1');
302
        $stat_fam             = $this->getBlockSetting($block_id, 'stat_fam', '1');
303
        $stat_sour            = $this->getBlockSetting($block_id, 'stat_sour', '1');
304
        $stat_media           = $this->getBlockSetting($block_id, 'stat_media', '1');
305
        $stat_repo            = $this->getBlockSetting($block_id, 'stat_repo', '1');
306
        $stat_surname         = $this->getBlockSetting($block_id, 'stat_surname', '1');
307
        $stat_events          = $this->getBlockSetting($block_id, 'stat_events', '1');
308
        $stat_users           = $this->getBlockSetting($block_id, 'stat_users', '1');
309
        $stat_first_birth     = $this->getBlockSetting($block_id, 'stat_first_birth', '1');
310
        $stat_last_birth      = $this->getBlockSetting($block_id, 'stat_last_birth', '1');
311
        $stat_first_death     = $this->getBlockSetting($block_id, 'stat_first_death', '1');
312
        $stat_last_death      = $this->getBlockSetting($block_id, 'stat_last_death', '1');
313
        $stat_long_life       = $this->getBlockSetting($block_id, 'stat_long_life', '1');
314
        $stat_avg_life        = $this->getBlockSetting($block_id, 'stat_avg_life', '1');
315
        $stat_most_chil       = $this->getBlockSetting($block_id, 'stat_most_chil', '1');
316
        $stat_avg_chil        = $this->getBlockSetting($block_id, 'stat_avg_chil', '1');
317
318
        return view('modules/gedcom_stats/config', [
319
            'show_last_update'     => $show_last_update,
320
            'show_common_surnames' => $show_common_surnames,
321
            'number_of_surnames'   => $number_of_surnames,
322
            'stat_indi'            => $stat_indi,
323
            'stat_fam'             => $stat_fam,
324
            'stat_sour'            => $stat_sour,
325
            'stat_media'           => $stat_media,
326
            'stat_repo'            => $stat_repo,
327
            'stat_surname'         => $stat_surname,
328
            'stat_events'          => $stat_events,
329
            'stat_users'           => $stat_users,
330
            'stat_first_birth'     => $stat_first_birth,
331
            'stat_last_birth'      => $stat_last_birth,
332
            'stat_first_death'     => $stat_first_death,
333
            'stat_last_death'      => $stat_last_death,
334
            'stat_long_life'       => $stat_long_life,
335
            'stat_avg_life'        => $stat_avg_life,
336
            'stat_most_chil'       => $stat_most_chil,
337
            'stat_avg_chil'        => $stat_avg_chil,
338
        ]);
339
    }
340
}
341