Passed
Push — master ( 7eb007...9f7f48 )
by Robbie
03:06 queued 10s
created

EnabledMembers::sourceRecords()   B

Complexity

Conditions 8
Paths 40

Size

Total Lines 58
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 34
c 1
b 0
f 0
nc 40
nop 1
dl 0
loc 58
rs 8.1315

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
2
3
namespace SilverStripe\MFA\Report;
4
5
use SilverStripe\Forms\CheckboxField;
6
use SilverStripe\Forms\DropdownField;
7
use SilverStripe\Forms\FieldList;
8
use SilverStripe\Forms\NumericField;
9
use SilverStripe\Forms\TextField;
10
use SilverStripe\MFA\Extension\MemberExtension;
11
use SilverStripe\MFA\Method\MethodInterface;
12
use SilverStripe\MFA\Service\MethodRegistry;
13
use SilverStripe\ORM\ArrayList;
14
use SilverStripe\ORM\Filterable;
15
use SilverStripe\ORM\SS_List;
16
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...
17
use SilverStripe\Security\Member;
18
use SilverStripe\View\ArrayData;
19
20
if (!class_exists(Report::class)) {
21
    return;
22
}
23
24
class EnabledMembers extends Report
25
{
26
    protected $title = 'Multi-factor authentication status of members';
27
28
    protected $description = 'Identifies all members on this site, with columns added to indicate the status each has'
29
        . ' in regards to multi-factor authentication method registration.';
30
31
    protected $dataClass = Member::class;
32
33
    public function title(): string
34
    {
35
        return _t(__CLASS__ . '.TITLE', parent::title());
36
    }
37
38
    public function description(): string
39
    {
40
        return _t(__CLASS__ . '.DESCRIPTION', parent::description());
41
    }
42
43
    /**
44
     * Supplies the list displayed in the report. Required method for {@see Report} to work, but is not inherited API
45
     *
46
     * @param array $params
47
     * @return SS_List
48
     */
49
    public function sourceRecords($params): SS_List
50
    {
51
        $methods = $this->getMethodClassToTitleMapping();
52
53
        // Any GridFieldAction will submit all form inputs, regardless of whether or not they have valid values.
54
        // This results in zero results when no filters have been set, as the query then filters for empty string
55
        // on all parameters. `stlen` is a safe function, as all user input comes through to PHP as strings.
56
        $params = array_filter($params, 'strlen');
57
58
        // Having a WHERE limits the results, which means the the COUNT will be off (GROUP BY applied after WHERE)
59
        // So clip the count filter and apply it after the database query, only if Methods filter is also set
60
        // this way we can delegate to the database where possible, which should be faster.
61
        if (isset($params['Count'])) {
62
            $desiredCount = (int)$params['Count'];
63
            if (isset($params['Methods'])) {
64
                $countFilter = $desiredCount;
65
                unset($params['Count']);
66
            } elseif ($desiredCount) {
67
                // When querying SQL COUNT() - this will always factor in the backup method, which is excluded in the
68
                // secondary query below to get the list of method names the user has registered. So to compensate we
69
                // need to bump up the entered number by 1.
70
                $params['Count'] = $desiredCount + 1;
71
            }
72
        }
73
74
        $filteredList = ArrayList::create([]);
75
        $backupClass = $this->getBackupMethodClass();
76
        /** @var Member[]&MemberExtension[] $members */
77
        $members = $this->applyParams(Member::get(), $params)->toArray();
78
        foreach ($members as $member) {
79
            $defaultMethod = $member->getDefaultRegisteredMethod();
80
            $defaultMethodClassName = $defaultMethod ? $defaultMethod->MethodClassName : '';
81
82
            $registeredMethods = $member
83
                ->RegisteredMFAMethods()
84
                ->exclude('MethodClassName', $backupClass)
85
                ->column('MethodClassName');
86
            $registeredMethodNames = array_map(function (string $methodClass) use ($methods): string {
87
                return $methods[$methodClass];
88
            }, $registeredMethods);
89
90
            $memberReportData = ArrayData::create();
91
            foreach ($member->summaryFields() as $field => $name) {
0 ignored issues
show
Bug introduced by
The method summaryFields() does not exist on SilverStripe\MFA\Extension\MemberExtension. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

91
            foreach ($member->/** @scrutinizer ignore-call */ summaryFields() as $field => $name) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
92
                $memberReportData->$field = $member->$field;
93
            }
94
95
            $memberReportData->methodCount = (string)count($registeredMethods);
96
            $memberReportData->registeredMethods = implode(', ', $registeredMethodNames);
97
            $memberReportData->defaultMethodName = $methods[$defaultMethodClassName] ?? '';
98
            $memberReportData->skippedRegistration = $member->dbObject('HasSkippedMFARegistration')->Nice();
0 ignored issues
show
Bug introduced by
The method dbObject() does not exist on SilverStripe\MFA\Extension\MemberExtension. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

98
            $memberReportData->skippedRegistration = $member->/** @scrutinizer ignore-call */ dbObject('HasSkippedMFARegistration')->Nice();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
99
100
            $filteredList->push($memberReportData);
101
        };
102
        if (isset($countFilter)) {
103
            $filteredList = $filteredList->filter('methodCount', $countFilter);
104
        }
105
        $this->extend('updateSourceRecords', $filteredList);
106
        return $filteredList;
107
    }
108
109
    /**
110
     * List the columns configured to display in the resulting reports GridField
111
     *
112
     * @return array
113
     */
114
    public function columns(): array
115
    {
116
        $columns = singleton(Member::class)->summaryFields() + [
117
            'methodCount' => _t(__CLASS__ . '.COLUMN_METHOD_COUNT', '№ methods'),
118
            'registeredMethods' => _t(__CLASS__ . '.COLUMN_METHODS_REGISTERED', 'Method names'),
119
            'defaultMethodName' => _t(__CLASS__ . '.COLUMN_METHOD_DEFAULT', 'Default method'),
120
            'skippedRegistration' => _t(__CLASS__ . '.COLUMN_SKIPPED_REGISTRATION', 'Skipped registration'),
121
        ];
122
        $this->extend('updateColumns', $columns);
123
        return $columns;
124
    }
125
126
    /**
127
     * Provides the fields used to gather input to filter the report
128
     *
129
     * @return FieldList
130
     */
131
    public function parameterFields(): FieldList
132
    {
133
        $methods = $this->getMethodClassToTitleMapping();
134
        unset($methods[$this->getBackupMethodClass()]);
135
        $parameterFields = FieldList::create(
136
            TextField::create(
137
                'Member',
138
                singleton(Member::class)->i18n_singular_name()
139
            )->setDescription(_t(
140
                __CLASS__ . '.FILTER_MEMBER_DESCRIPTION',
141
                'Firstname, Surname, Email partial match search'
142
            )),
143
            NumericField::create(
144
                'Count',
145
                _t(__CLASS__ . '.COLUMN_METHOD_COUNT', 'Number of registered methods')
146
            )->setHTML5(true),
147
            DropdownField::create(
148
                'Methods',
149
                _t(__CLASS__ . '.COLUMN_METHODS_REGISTERED', 'Registered methods'),
150
                $methods,
151
                ''
152
            )->setHasEmptyDefault(true),
153
            CheckboxField::create(
154
                'Skipped',
155
                _t(__CLASS__ . '.COLUMN_SKIPPED_REGISTRATION', 'Skipped registration')
156
            )
157
        );
158
        $this->extend('updateParameterFields', $parameterFields);
159
        return $parameterFields;
160
    }
161
162
    /**
163
     * Create an array mapping authentication method Class Names to Readable Names
164
     *
165
     * @return array
166
     */
167
    protected function getMethodClassToTitleMapping(): array
168
    {
169
        $methods = singleton(MethodRegistry::class)->getMethods();
170
        $methodsClasses = array_map(
171
            function (MethodInterface $method): string {
172
                return get_class($method);
173
            },
174
            $methods
175
        );
176
        $methodNames = array_map(
177
            function (MethodInterface $method): string {
178
                return $method->getName();
179
            },
180
            $methods
181
        );
182
        return array_combine($methodsClasses, $methodNames);
0 ignored issues
show
Bug Best Practice introduced by
The expression return array_combine($me...sClasses, $methodNames) could return the type false which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
183
    }
184
185
    /**
186
     * Applies parameters to source records for filtering purposes.
187
     *
188
     * @param Filterable $params
189
     * @param array $params
190
     * @return SS_List
191
     */
192
    protected function applyParams(Filterable $sourceList, array $params): SS_List
193
    {
194
        $map = [
195
            'Member' => ['FirstName:PartialMatch', 'Surname:PartialMatch', 'Email:PartialMatch'],
196
            'Count' => 'RegisteredMFAMethods.Count()',
197
            'Methods' => 'RegisteredMFAMethods.MethodClassName',
198
            'Skipped' => 'HasSkippedMFARegistration',
199
        ];
200
        $this->extend('updateParameterMap', $map);
201
        foreach ($map as $submissionName => $searchKey) {
202
            if (isset($params[$submissionName])) {
203
                if (is_array($searchKey)) {
204
                    $sourceList = $sourceList->filterAny(array_fill_keys($searchKey, $params[$submissionName]));
0 ignored issues
show
Unused Code introduced by
The call to SilverStripe\ORM\Filterable::filterAny() has too many arguments starting with array_fill_keys($searchK...arams[$submissionName]). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

204
                    /** @scrutinizer ignore-call */ 
205
                    $sourceList = $sourceList->filterAny(array_fill_keys($searchKey, $params[$submissionName]));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
205
                } else {
206
                    $sourceList = $sourceList->filter($searchKey, $params[$submissionName]);
0 ignored issues
show
Unused Code introduced by
The call to SilverStripe\ORM\Filterable::filter() has too many arguments starting with $searchKey. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

206
                    /** @scrutinizer ignore-call */ 
207
                    $sourceList = $sourceList->filter($searchKey, $params[$submissionName]);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
207
                }
208
            }
209
        }
210
        return $sourceList;
211
    }
212
213
    /**
214
     * Returns the PHP class name of the configured backup method in the MFA module
215
     *
216
     * @return string
217
     */
218
    private function getBackupMethodClass(): string
219
    {
220
        return get_class(singleton(MethodRegistry::class)->getBackupMethod());
221
    }
222
}
223