Passed
Push — master ( e31b0a...908be6 )
by Robbie
10:54 queued 10s
created

EnabledMembers::formatMethodsColumn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 8
rs 10
1
<?php declare(strict_types=1);
2
3
namespace SilverStripe\MFA\Report;
4
5
use SilverStripe\Forms\DropdownField;
6
use SilverStripe\Forms\FieldList;
7
use SilverStripe\Forms\TextField;
8
use SilverStripe\MFA\Extension\MemberExtension;
9
use SilverStripe\MFA\Model\RegisteredMethod;
10
use SilverStripe\MFA\Service\MethodRegistry;
11
use SilverStripe\ORM\ArrayList;
12
use SilverStripe\ORM\DataList;
13
use SilverStripe\Reports\Report;
0 ignored issues
show
Bug introduced by
The type SilverStripe\Reports\Report was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
14
use SilverStripe\Security\Member;
15
16
if (!class_exists(Report::class)) {
17
    return;
18
}
19
20
class EnabledMembers extends Report
21
{
22
    protected $title = 'Multi-factor authentication status of members';
23
24
    protected $description = 'Identifies all members on this site, with columns added to indicate the status each has'
25
        . ' in regards to multi-factor authentication method registration.';
26
27
    protected $dataClass = Member::class;
28
29
    /**
30
     * Cached registered methods fetched for the current list of members. This should only be populated at render time
31
     * as methods will be fetched for the current "records" on the report
32
     *
33
     * @var DataList|null
34
     */
35
    private $registeredMethods = null;
36
37
    public function title(): string
38
    {
39
        return _t(__CLASS__ . '.TITLE', parent::title());
40
    }
41
42
    public function description(): string
43
    {
44
        return _t(__CLASS__ . '.DESCRIPTION', parent::description());
45
    }
46
47
    /**
48
     * Supplies the list displayed in the report
49
     *
50
     * @param array $params
51
     * @return DataList
52
     */
53
    public function sourceRecords($params): DataList
54
    {
55
        $sourceList = Member::get();
56
57
        // Reports treat 0 as empty, which is interpreted as "any", so we switch it to "no" and "yes" for filtering
58
        if (!empty($params['Skipped'])) {
59
            $sourceList = $sourceList->filter('HasSkippedMFARegistration', $params['Skipped'] === 'no' ? 0 : 1);
60
        }
61
62
        // Allow partial matching on standard member fields
63
        if (!empty($params['Member'])) {
64
            $sourceList = $sourceList->filterAny([
65
                'FirstName:PartialMatch' => $params['Member'],
66
                'Surname:PartialMatch' => $params['Member'],
67
                'Email:PartialMatch' => $params['Member'],
68
            ]);
69
        }
70
71
        // Apply "none", "any", or a specific registered method filter
72
        if (!empty($params['Methods'])) {
73
            if ($params['Methods'] === 'none') {
74
                $sourceList = $sourceList->filter('RegisteredMFAMethods.MethodClassName', null);
75
            } elseif ($params['Methods'] === 'any') {
76
                $sourceList = $sourceList->filter('RegisteredMFAMethods.MethodClassName:not', null);
77
            } else {
78
                $sourceList = $sourceList->filter('RegisteredMFAMethods.MethodClassName', $params['Methods']);
79
            }
80
        }
81
82
        $this->extend('updateSourceRecords', $sourceList);
83
84
        return $sourceList;
85
    }
86
87
    /**
88
     * List the columns configured to display in the resulting reports GridField
89
     *
90
     * @return array
91
     */
92
    public function columns(): array
93
    {
94
        $columns = singleton(Member::class)->summaryFields() + [
95
            'registeredMethods' => [
96
                'title' => _t(__CLASS__ . '.COLUMN_METHODS_REGISTERED', 'Registered methods'),
97
                'formatting' => [$this, 'formatMethodsColumn']
98
            ],
99
            'defaultMethodName' => [
100
                'title' => _t(__CLASS__ . '.COLUMN_METHOD_DEFAULT', 'Default method'),
101
                'formatting' => [$this, 'formatDefaultMethodColumn']
102
            ],
103
            'HasSkippedMFARegistration' => [
104
                'title' => _t(__CLASS__ . '.COLUMN_SKIPPED_REGISTRATION', 'Skipped registration'),
105
                'formatting' => function ($_, $record) {
106
                    return $record->HasSkippedMFARegistration ? 'Yes' : 'No';
107
                },
108
            ],
109
        ];
110
111
        $this->extend('updateColumns', $columns);
112
113
        return $columns;
114
    }
115
116
    /**
117
     * Provides the fields used to gather input to filter the report
118
     *
119
     * @return FieldList
120
     */
121
    public function parameterFields(): FieldList
122
    {
123
        $parameterFields = FieldList::create([
124
            TextField::create(
125
                'Member',
126
                singleton(Member::class)->i18n_singular_name()
127
            )->setDescription(_t(
128
                __CLASS__ . '.FILTER_MEMBER_DESCRIPTION',
129
                'Firstname, Surname, Email partial match search'
130
            )),
131
            DropdownField::create(
132
                'Methods',
133
                _t(__CLASS__ . '.COLUMN_METHODS_REGISTERED', 'Registered methods'),
134
                $this->getRegisteredMethodOptions()
135
            )->setHasEmptyDefault(true),
136
            DropdownField::create(
137
                'Skipped',
138
                _t(__CLASS__ . '.COLUMN_SKIPPED_REGISTRATION', 'Skipped registration'),
139
                [ 'no' => 'No', 'yes' => 'Yes' ]
140
            )->setHasEmptyDefault(true),
141
        ]);
142
143
        $this->extend('updateParameterFields', $parameterFields);
144
145
        return $parameterFields;
146
    }
147
148
    /**
149
     * Produce a string that indicates the names of registered methods for a given member
150
     *
151
     * @param null $_
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $_ is correct as it would always require null to be passed?
Loading history...
152
     * @param Member $record
153
     * @return string
154
     */
155
    public function formatMethodsColumn($_, Member $record): string
156
    {
157
        /** @var Member&MemberExtension $record */
158
        $methods = $this->getRegisteredMethodsForRecords();
159
160
        return implode(', ', array_map(function (RegisteredMethod $method) {
161
            return $method->getMethod()->getName();
162
        }, $methods->filter('MemberID', $record->ID)->toArray()));
163
    }
164
165
    /**
166
     * Produce a string that indicates the name of the default registered method for a member
167
     *
168
     * @param null $_
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $_ is correct as it would always require null to be passed?
Loading history...
169
     * @param Member&MemberExtension $record
170
     * @return string
171
     */
172
    public function formatDefaultMethodColumn($_, Member $record): string
173
    {
174
        /** @var RegisteredMethod|null $method */
175
        $method = $this->getRegisteredMethodsForRecords()->byID($record->DefaultRegisteredMethodID);
176
177
        if (!$method) {
178
            return '';
179
        }
180
181
        return $method->getMethod()->getName();
182
    }
183
184
    /**
185
     * Create an array mapping authentication method Class Names to Readable Names
186
     *
187
     * @return array
188
     */
189
    protected function getMethodClassToTitleMapping(): array
190
    {
191
        $mapping = [];
192
193
        foreach (MethodRegistry::singleton()->getMethods() as $method) {
194
            $mapping[get_class($method)] = $method->getName();
195
        }
196
197
        return $mapping;
198
    }
199
200
    /**
201
     * @return ArrayList
202
     */
203
    protected function getRegisteredMethodsForRecords(): ArrayList
204
    {
205
        if ($this->registeredMethods instanceof ArrayList) {
206
            return $this->registeredMethods;
207
        }
208
209
        // Get the members from the generated report field list
210
        /** @var DataList $members $members */
211
        $members = $this->getReportField()->getList();
212
213
        // Filter RegisteredMethods by the IDs of those members and convert it to an ArrayList (to prevent filters ahead
214
        // from executing the datalist more than once)
215
        $this->registeredMethods = ArrayList::create(
216
            RegisteredMethod::get()
217
                ->filter('MemberID', $members->column())
218
                ->exclude('MethodClassName', $this->getBackupMethodClass())
219
                ->toArray()
220
        );
221
222
        return $this->registeredMethods;
223
    }
224
225
    /**
226
     * Adds "None" and "Any" options to the registered method dropdown filter
227
     *
228
     * @return array
229
     */
230
    private function getRegisteredMethodOptions(): array
231
    {
232
        $methods = [
233
            'none' => _t(__CLASS__ . '.NONE', 'None'),
234
            'any' => _t(__CLASS__ . '.ANY_AT_LEAST_ONE', 'Any (at least one)'),
235
        ] + $this->getMethodClassToTitleMapping();
236
        unset($methods[$this->getBackupMethodClass()]);
237
238
        return $methods;
239
    }
240
241
    /**
242
     * Returns the PHP class name of the configured backup method in the MFA module
243
     *
244
     * @return string
245
     */
246
    private function getBackupMethodClass(): string
247
    {
248
        return get_class(singleton(MethodRegistry::class)->getBackupMethod());
249
    }
250
}
251