Passed
Push — master ( 00a79d...f5402f )
by Greg
05:33
created

TopSurnamesModule::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2019 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 <http://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\Functions\FunctionsPrintLists;
24
use Fisharebest\Webtrees\I18N;
25
use Fisharebest\Webtrees\Tree;
26
use Fisharebest\Webtrees\Services\ModuleService;
27
use Illuminate\Database\Capsule\Manager as DB;
28
use Illuminate\Database\Query\Expression;
29
use Illuminate\Support\Str;
30
use Psr\Http\Message\ServerRequestInterface;
31
32
/**
33
 * Class TopSurnamesModule
34
 */
35
class TopSurnamesModule extends AbstractModule implements ModuleBlockInterface
36
{
37
    use ModuleBlockTrait;
38
39
    // Default values for new blocks.
40
    private const DEFAULT_NUMBER = '10';
41
    private const DEFAULT_STYLE  = 'table';
42
43
    /** @var ModuleService */
44
    private $module_service;
45
46
    /**
47
     * TopSurnamesModule constructor.
48
     *
49
     * @param ModuleService $module_service
50
     */
51
    public function __construct(ModuleService $module_service)
52
    {
53
        $this->module_service = $module_service;
54
    }
55
56
    /**
57
     * How should this module be identified in the control panel, etc.?
58
     *
59
     * @return string
60
     */
61
    public function title(): string
62
    {
63
        /* I18N: Name of a module. Top=Most common */
64
        return I18N::translate('Top surnames');
65
    }
66
67
    /**
68
     * A sentence describing what this module does.
69
     *
70
     * @return string
71
     */
72
    public function description(): string
73
    {
74
        /* I18N: Description of the “Top surnames” module */
75
        return I18N::translate('A list of the most popular surnames.');
76
    }
77
78
    /**
79
     * Generate the HTML content of this block.
80
     *
81
     * @param Tree     $tree
82
     * @param int      $block_id
83
     * @param string   $context
84
     * @param string[] $config
85
     *
86
     * @return string
87
     */
88
    public function getBlock(Tree $tree, int $block_id, string $context, array $config = []): string
89
    {
90
        $num       = (int) $this->getBlockSetting($block_id, 'num', self::DEFAULT_NUMBER);
91
        $infoStyle = $this->getBlockSetting($block_id, 'infoStyle', self::DEFAULT_STYLE);
92
93
        extract($config, EXTR_OVERWRITE);
94
95
        // Use the count of base surnames.
96
        $top_surnames = DB::table('name')
97
            ->where('n_file', '=', $tree->id())
98
            ->where('n_type', '<>', '_MARNM')
99
            ->whereNotIn('n_surn', ['@N.N.', ''])
100
            ->groupBy(['n_surn'])
101
            ->orderByDesc(new Expression('COUNT(n_surn)'))
102
            ->take($num)
103
            ->pluck('n_surn');
104
105
        $all_surnames = [];
106
107
        foreach ($top_surnames as $top_surname) {
108
            $variants = DB::table('name')
109
                ->where('n_file', '=', $tree->id())
110
                ->where(new Expression('n_surn /*! COLLATE utf8_bin */'), '=', $top_surname)
111
                ->groupBy(['surname'])
112
                ->select([new Expression('n_surname /*! COLLATE utf8_bin */ AS surname'), new Expression('count(*) AS total')])
113
                ->pluck('total', 'surname')
114
                ->all();
115
116
            $all_surnames[$top_surname] = $variants;
117
        }
118
        
119
        // Find a module providing individual lists.
120
        $module = $this->module_service
121
            ->findByComponent(ModuleListInterface::class, $tree, Auth::user())
122
            ->first(static function (ModuleInterface $module): bool {
123
                return $module instanceof IndividualListModule;
124
            });
125
        
126
        switch ($infoStyle) {
127
            case 'tagcloud':
128
                uksort($all_surnames, [I18N::class, 'strcasecmp']);
129
                $content = FunctionsPrintLists::surnameTagCloud($all_surnames, $module, true, $tree);
130
                break;
131
            case 'list':
132
                uasort($all_surnames, [$this, 'surnameCountSort']);
133
                $content = FunctionsPrintLists::surnameList($all_surnames, 1, true, $module, $tree);
134
                break;
135
            case 'array':
136
                uasort($all_surnames, [$this, 'surnameCountSort']);
137
                $content = FunctionsPrintLists::surnameList($all_surnames, 2, true, $module, $tree);
138
                break;
139
            case 'table':
140
            default:
141
                $content = view('lists/surnames-table', [
142
                    'surnames' => $all_surnames,
143
                    'module'   => $module,
144
                    'families' => false,
145
                    'tree'     => $tree,
146
                ]);
147
                break;
148
        }
149
150
        if ($context !== self::CONTEXT_EMBED) {
151
            $num = count($top_surnames);
152
            if ($num === 1) {
153
                // I18N: i.e. most popular surname.
154
                $title = I18N::translate('Top surname');
155
            } else {
156
                // I18N: Title for a list of the most common surnames, %s is a number. Note that a separate translation exists when %s is 1
157
                $title = I18N::plural('Top %s surname', 'Top %s surnames', $num, I18N::number($num));
158
            }
159
160
            return view('modules/block-template', [
161
                'block'      => Str::kebab($this->name()),
162
                'id'         => $block_id,
163
                'config_url' => $this->configUrl($tree, $context, $block_id),
164
                'title'      => $title,
165
                'content'    => $content,
166
            ]);
167
        }
168
169
        return $content;
170
    }
171
172
    /**
173
     * Should this block load asynchronously using AJAX?
174
     *
175
     * Simple blocks are faster in-line, more complex ones can be loaded later.
176
     *
177
     * @return bool
178
     */
179
    public function loadAjax(): bool
180
    {
181
        return false;
182
    }
183
184
    /**
185
     * Can this block be shown on the user’s home page?
186
     *
187
     * @return bool
188
     */
189
    public function isUserBlock(): bool
190
    {
191
        return true;
192
    }
193
194
    /**
195
     * Can this block be shown on the tree’s home page?
196
     *
197
     * @return bool
198
     */
199
    public function isTreeBlock(): bool
200
    {
201
        return true;
202
    }
203
204
    /**
205
     * Update the configuration for a block.
206
     *
207
     * @param ServerRequestInterface $request
208
     * @param int     $block_id
209
     *
210
     * @return void
211
     */
212
    public function saveBlockConfiguration(ServerRequestInterface $request, int $block_id): void
213
    {
214
        $params = $request->getParsedBody();
215
216
        $this->setBlockSetting($block_id, 'num', $params['num']);
217
        $this->setBlockSetting($block_id, 'infoStyle', $params['infoStyle']);
218
    }
219
220
    /**
221
     * An HTML form to edit block settings
222
     *
223
     * @param Tree $tree
224
     * @param int  $block_id
225
     *
226
     * @return string
227
     */
228
    public function editBlockConfiguration(Tree $tree, int $block_id): string
229
    {
230
        $num       = $this->getBlockSetting($block_id, 'num', self::DEFAULT_NUMBER);
231
        $infoStyle = $this->getBlockSetting($block_id, 'infoStyle', self::DEFAULT_STYLE);
232
233
        $info_styles = [
234
            /* I18N: An option in a list-box */
235
            'list'     => I18N::translate('bullet list'),
236
            /* I18N: An option in a list-box */
237
            'array'    => I18N::translate('compact list'),
238
            /* I18N: An option in a list-box */
239
            'table'    => I18N::translate('table'),
240
            /* I18N: An option in a list-box */
241
            'tagcloud' => I18N::translate('tag cloud'),
242
        ];
243
244
        return view('modules/top10_surnames/config', [
245
            'num'         => $num,
246
            'infoStyle'   => $infoStyle,
247
            'info_styles' => $info_styles,
248
        ]);
249
    }
250
251
    /**
252
     * Sort (lists of counts of similar) surname by total count.
253
     *
254
     * @param string[] $a
255
     * @param string[] $b
256
     *
257
     * @return int
258
     */
259
    private function surnameCountSort(array $a, array $b): int
260
    {
261
        return array_sum($b) - array_sum($a);
262
    }
263
}
264