Test Failed
Push — master ( 239970...076b28 )
by Tomasz
10:54
created

IdPlist   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 395
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 60
eloc 208
c 3
b 0
f 0
dl 0
loc 395
rs 3.6

11 Methods

Rating   Name   Duplication   Size   Complexity  
A orderIdentityProviders() 0 17 3
B setProfileAttributes() 0 39 8
B listIdentityProvidersWithProfiles() 0 50 9
A geoDistance() 0 7 1
B setIdentityProviderAttributes() 0 32 6
C listAllIdentityProviders() 0 64 14
B getIdpDistance() 0 23 7
A setKeywords() 0 20 5
A setAllIdentyProvidersQuery() 0 22 3
A setCurrentLocation() 0 10 3
A setAllProfileQuery() 0 18 1

How to fix   Complexity   

Complex Class

Complex classes like IdPlist 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 IdPlist, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * *****************************************************************************
5
 * Contributions to this work were made on behalf of the GÉANT project, a 
6
 * project that has received funding from the European Union’s Framework 
7
 * Programme 7 under Grant Agreements No. 238875 (GN3) and No. 605243 (GN3plus),
8
 * Horizon 2020 research and innovation programme under Grant Agreements No. 
9
 * 691567 (GN4-1) and No. 731122 (GN4-2).
10
 * On behalf of the aforementioned projects, GEANT Association is the sole owner
11
 * of the copyright in all material which was developed by a member of the GÉANT
12
 * project. GÉANT Vereniging (Association) is registered with the Chamber of 
13
 * Commerce in Amsterdam with registration number 40535155 and operates in the 
14
 * UK as a branch of GÉANT Vereniging.
15
 * 
16
 * Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands. 
17
 * UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK
18
 *
19
 * License: see the web/copyright.inc.php file in the file structure or
20
 *          <base_url>/copyright.php after deploying the software
21
 */
22
23
namespace core;
24
25
class IdPlist extends common\Entity
26
{
27
28
    /**
29
     * Order active identity providers according to their distance and name
30
     * @param string $country         the country from which to list IdPs
31
     * @param array  $currentLocation current location
32
     * @return array $IdPs -  list of arrays ('id', 'name');
33
     */
34
    public static function orderIdentityProviders($country, $currentLocation)
35
    {
36
        $idps = self::listAllIdentityProviders(1, $country);
37
        $here = self::setCurrentLocation($currentLocation);
38
        $idpTitle = [];
39
        $resultSet = [];
40
        foreach ($idps as $idp) {
41
            $idpTitle[$idp['entityID']] = $idp['title'];
42
            $d = self::getIdpDistance($idp, $here);
43
            $resultSet[$idp['entityID']] = $d." ".$idp['title'];
44
        }
45
        asort($resultSet);
46
        $outarray = [];
47
        foreach (array_keys($resultSet) as $r) {
48
            $outarray[] = ['idp' => $r, 'title' => $idpTitle[$r]];
49
        }
50
        return($outarray);
51
    }
52
53
    /**
54
     * Lists all identity providers in the database
55
     * adding information required by DiscoJuice.
56
     * 
57
     * @param int    $activeOnly if set to non-zero will cause listing of only those institutions which have some valid profiles defined.
58
     * @param string $country    if set, only list IdPs in a specific country
59
     * @return array the list of identity providers
60
     *
61
     */
62
    public static function listAllIdentityProviders($activeOnly = 0, $country = "")
63
    {
64
        common\Entity::intoThePotatoes();
65
        $handle = DBConnection::handle("INST");
66
        $handle->exec("SET SESSION group_concat_max_len=10000");
67
        $query = IdPlist::setAllIdentyProvidersQuery($activeOnly, $country);
68
        $allIDPs = ($country != "" ? $handle->exec($query, "s", $country) : $handle->exec($query));
69
        $idpArray = [];
70
        // SELECTs never return a booleans, always an object
71
        $allIdPresults = [];
72
        while ($queryResult = mysqli_fetch_object(/** @scrutinizer ignore-type */ $allIDPs)) {
73
            $allIdPresults[] = $queryResult;
74
        }
75
        foreach ($allIdPresults as $queryResult) {
76
            $options = IdPlist::setIdentityProviderAttributes($queryResult);
77
            $oneInstitutionResult = [];
78
            $oneInstitutionResult['entityID'] = $queryResult->inst_id;
79
            $oneInstitutionResult['country'] = strtoupper($queryResult->country);
80
            if ($options['icon'] > 0) {
81
                $oneInstitutionResult['icon'] = $options['icon'];
82
            }
83
            $name = _("Unnamed Entity");
84
            if (count($options['names']) != 0) {
85
                $langObject = new \core\common\Language();
86
                $name = $langObject->getLocalisedValue($options['names']);
87
            }          
88
            $oneInstitutionResult['title'] = $name;
89
            
90
            if (\config\ConfAssistant::USE_KEYWORDS) {
91
                $keywords = [];
92
                foreach ($options['names'] as $keyword) {
93
                    $value = $keyword['value'];
94
                    $keywords[$keyword['lang']] = $keyword['value'];
95
                    $keywords[$keyword['lang'].'_7'] =
96
                        iconv('UTF-8', 'ASCII//TRANSLIT', $value);
97
                }
98
                
99
                foreach ($options['altnames'] as $keyword) {
100
                    $value = $keyword['value'];
101
                    $keywords[] = $value;
102
                }
103
                
104
                $q = "SELECT DISTINCT realm FROM profile WHERE inst_id=? AND realm NOT LIKE '%hosted.eduroam.org'";
105
                $realms = $handle->exec($q, 'i', $queryResult->inst_id);
106
                while ($outerId = mysqli_fetch_row(/** @scrutinizer ignore-type */ $realms)) {
107
                    if (preg_match('/.*@(.*)$/', $outerId[0], $matches)) {
108
                        $keywords[] = $matches[1];
109
                    }
110
                }
111
                $keywords_final = array_unique($keywords);
112
                if (!empty($keywords_final)) {
113
                    $oneInstitutionResult['keywords'] = [];
114
                    foreach (array_keys($keywords_final) as $key) {
115
                    $oneInstitutionResult['keywords'][] = [$keywords_final[$key]];
116
                    }
117
                }
118
            }
119
            if (count($options['geo']) > 0) {
120
                $oneInstitutionResult['geo'] = $options['geo'];
121
            }
122
            $idpArray[] = $oneInstitutionResult;
123
        }
124
        common\Entity::outOfThePotatoes();
125
        return $idpArray;
126
    }
127
    
128
    /**
129
     * outputs a full list of IdPs containing the fllowing data:
130
     * institution_is, institution name in all available languages,
131
     * list of production profiles.
132
     * For eache profile the profile identifier, profile name in all languages
133
     * and redirect values (empty rediret value means that no redirect has been
134
     * set).
135
     * 
136
     * @return array of identity providers with attributes
137
     */
138
    public static function listIdentityProvidersWithProfiles() {
139
        $handle = DBConnection::handle("INST");
140
        $handle->exec("SET SESSION group_concat_max_len=10000");
141
        $idpQuery = IdPlist::setAllIdentyProvidersQuery(1);
142
        $allIDPs = $handle->exec($idpQuery);
143
        $idpArray = [];
144
        while ($queryResult = mysqli_fetch_object(/** @scrutinizer ignore-type */ $allIDPs)) {
145
            $options = IdPlist::setIdentityProviderAttributes($queryResult);
146
            $oneInstitutionResult = [];
147
            $oneInstitutionResult['country'] = strtoupper($queryResult->country);
148
            $oneInstitutionResult['entityID'] = (int) $queryResult->inst_id;
149
            if ($options['icon'] > 0) {
150
                $oneInstitutionResult['icon'] = $options['icon'];
151
            }
152
            $oneInstitutionResult['names'] = $options['names'];
153
            if (count($options['geo']) > 0) {
154
                $geoArray = [];
155
                foreach ($options['geo'] as $coords) {
156
                    $geoArray[] = ['lon' => (float) $coords['lon'],
157
                        'lat' => (float) $coords['lat']];
158
                }
159
                $oneInstitutionResult['geo'] = $geoArray;
160
            }
161
162
            $idpArray[$queryResult->inst_id] = $oneInstitutionResult;
163
        }
164
        
165
        $profileQuery = IdPlist::setAllProfileQuery();
166
        $allProfiles = $handle->exec($profileQuery);
167
        while ($queryResult = mysqli_fetch_object(/** @scrutinizer ignore-type */ $allProfiles)) {
168
            $profileOptions = IdPlist::setProfileAttributes($queryResult);
169
            $idpId = $queryResult->inst_id;
170
            if (empty($idpArray[$idpId])) {
171
                continue;
172
            }
173
            if (empty($idpArray[$idpId]['profiles'])) {
174
                $idpArray[$idpId]['profiles'] = [];
175
            }
176
            if (!$profileOptions['production']) {
177
                continue;
178
            }
179
            $idpArray[$idpId]['profiles'][] = [
180
                'id'=> (int) $queryResult->profile_id,
181
                'names' => $profileOptions['profileNames'],
182
                'redirect' => $profileOptions['redirect'],
183
                'openroaming' => $profileOptions['openroaming'],
184
//                'options' => $profileOptions['options'],
185
                ];
186
        }       
187
        return $idpArray;
188
    }
189
190
    /**
191
     * sets the current location
192
     * 
193
     * @param array $currentLocation the location to set
194
     * @return array
195
     */
196
    private static function setCurrentLocation($currentLocation)
197
    {
198
        if (is_null($currentLocation)) {
199
            $currentLocation = ['lat' => "90", 'lon' => "0"];
200
            $userLocation = DeviceLocation::locateDevice();
201
            if ($userLocation['status'] == 'ok') {
202
                $currentLocation = $userLocation['geo'];
203
            }
204
        }
205
        return $currentLocation;
206
    }
207
208
    /**
209
     * calculate surface distance from user location to IdP location
210
     * @param array $idp      the IdP in question
211
     * @param array $location user location
212
     * @return string
213
     */
214
    private static function getIdpDistance($idp, $location)
215
    {
216
        $dist = 10000;
217
        if (isset($idp['geo'])) {
218
            $G = $idp['geo'];
219
            if (isset($G['lon'])) {
220
                $d1 = self::geoDistance($location, $G);
221
                if ($d1 < $dist) {
222
                    $dist = $d1;
223
                }
224
            } else {
225
                foreach ($G as $g) {
226
                    $d1 = self::geoDistance($location, $g);
227
                    if ($d1 < $dist) {
228
                        $dist = $d1;
229
                    }
230
                }
231
            }
232
        }
233
        if ($dist > 100) {
234
            $dist = 10000;
235
        }
236
        return(sprintf("%06d", $dist));
237
    }
238
    
239
    /**
240
     * set the IdP query string for listAllIdentityProviders and 
241
     * listIdentityProvidersWithProfiles
242
     * @param int    $activeOnly if set to non-zero will cause listing of only those institutions which have some valid profiles defined.
243
     * @param string $country    if set, only list IdPs in a specific country
244
     * 
245
     * @return string the query
246
     */
247
    private static function setAllIdentyProvidersQuery($activeOnly = 0, $country = "")
248
    {
249
        $query = "SELECT distinct institution.inst_id AS inst_id,
250
            institution.country AS country,
251
            group_concat(concat_ws('===',institution_option.option_name,
252
                LEFT(institution_option.option_value,200),
253
                institution_option.option_lang) separator '---') AS options
254
            FROM institution ";
255
        if ($activeOnly == 1) {
256
            $query .= "JOIN v_active_inst ON institution.inst_id = v_active_inst.inst_id ";
257
        }
258
        $query .= 
259
            "JOIN institution_option ON institution.inst_id = institution_option.institution_id
260
            WHERE (institution_option.option_name = 'general:instname' 
261
                OR institution_option.option_name = 'general:geo_coordinates'
262
                OR institution_option.option_name = 'general:instaltname'
263
                OR institution_option.option_name = 'general:logo_file') ";
264
265
        $query .= ($country != "" ? "AND institution.country = ? " : "");
266
267
        $query .= "GROUP BY institution.inst_id ORDER BY inst_id";
268
        return $query;
269
    }
270
271
    /**
272
     * set the Profile query string for listIdentityProvidersWithProfiles
273
     * 
274
     * @return string query
275
     */
276
    private static function setAllProfileQuery() {
277
        $query = "SELECT profile.inst_id AS inst_id,
278
            profile.profile_id,
279
            group_concat(concat_ws('===',profile_option.option_name, 
280
                LEFT(profile_option.option_value, 200),
281
                profile_option.option_lang) separator '---') AS profile_options,
282
                profile.realm AS realm
283
            FROM profile
284
            JOIN profile_option ON profile.profile_id = profile_option.profile_id
285
            WHERE profile.sufficient_config = 1
286
                AND profile_option.eap_method_id = 0
287
                AND (profile_option.option_name = 'profile:name'
288
                OR (profile_option.option_name = 'device-specific:redirect'
289
                    AND isnull(profile_option.device_id))
290
                OR profile_option.option_name = 'media:openroaming'
291
                OR profile_option.option_name = 'profile:production')        
292
            GROUP BY profile.profile_id ORDER BY inst_id";
293
        return $query;
294
    }
295
    
296
    /**
297
     * Extract IdP attributes for listAllIdentityProviders and 
298
     * listIdentityProvidersWithProfiles
299
     * @param  $idp object - the row_id object returned by the IdP search
300
     * @return array the IdP attributes
301
     */
302
    private static function setIdentityProviderAttributes($idp) {
303
        $options = explode('---', $idp->options);
304
        $names = [];
305
        $altnames = [];
306
        $geo = [];
307
        $icon = 0;
308
        foreach ($options as $option) {
309
            $opt = explode('===', $option);
310
            switch ($opt[0]) {
311
                case 'general:logo_file':
312
                    $icon = $idp->inst_id;
313
                    break;
314
                case 'general:geo_coordinates':
315
                    $at1 = json_decode($opt[1], true);
316
                    $geo[] = $at1;
317
                    break;
318
                case 'general:instname':
319
                    $names[] = [
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
320
                        'lang' => $opt[2],
321
                        'value' => $opt[1]
322
                    ];
323
                case 'general:instaltname':
324
                    $altnames[] = [
325
                        'lang' => $opt[2],
326
                        'value' => $opt[1]
327
                    ];
328
                    break;
329
                default:
330
                    break;
331
            }
332
        }
333
        return ['names' => $names, 'altnames'=> $altnames, 'geo' => $geo, 'icon' => $icon];       
334
    }
335
    
336
    /**
337
     * Extract Profile attributes for listIdentityProvidersWithProfiles
338
     * 
339
     * @param object $profile - the row_id object returned by the profile search
340
     * @return array the profile attributes
341
     */
342
    private static function setProfileAttributes($profile)
343
    {
344
        $profileOptions = explode('---', $profile->profile_options);
345
        $productionProfile = false;
346
        $profileNames = [];
347
        $redirect = '';
348
        $openroaming = 'none';
349
350
        foreach ($profileOptions as $profileOption) {
351
            $opt = explode('===', $profileOption);
352
            switch ($opt[0]) {
353
                case 'profile:production':
354
                    if ($opt[1] == 'on') {
355
                        $productionProfile = true;
356
                    }
357
                    break;
358
                case 'device-specific:redirect':
359
                    $redirect = $opt[1];
360
                    if (!empty($profile->device_id)) {
361
                        $redirect .= ':'.$profile->device_id;
362
                    }
363
                    break;
364
                case 'profile:name': 
365
                    $profileNames[] = [
366
                        'lang' => $opt[2],
367
                        'value' => $opt[1]
368
                    ];
369
                    break;
370
                case 'media:openroaming':
371
                    $openroaming = $opt[1];
372
                    break;
373
                default:
374
                    break; 
375
            }
376
        }
377
        return ['production' => $productionProfile,
378
            'profileNames' => $profileNames,
379
            'redirect' => $redirect,
380
            'openroaming' => $openroaming,
381
            ];
382
    }
383
    
384
    private static function setKeywords($names)
0 ignored issues
show
Unused Code introduced by
The method setKeywords() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
385
    {
386
        if (!\config\ConfAssistant::USE_KEYWORDS) {
387
            return null;
388
        }
389
        $keywords = [];
390
        $returnArray = [];
391
        foreach ($names as $keyword) {
392
            $value = $keyword['value'];
393
            $keywords[$keyword['lang']] = $keyword['value'];
394
            $keywords[$keyword['lang'].'_7'] =
395
                    iconv('UTF-8', 'ASCII//TRANSLIT', $value);
396
        }
397
        $keywords_final = array_unique($keywords);
398
        if (!empty($keywords_final)) {
399
            foreach (array_keys($keywords_final) as $key) {
400
            $returnArray[] = [$keywords_final[$key]];
401
            }
402
        }
403
        return $returnArray;
404
    }
405
406
    /**
407
     * Calculate the distance in km between two points given their
408
     * geo coordinates.
409
     * @param array $point1   first point as an 'lat', 'lon' array 
410
     * @param array $profile1 second point as an 'lat', 'lon' array 
411
     * @return float distance in km
412
     */
413
    public static function geoDistance($point1, $profile1)
414
    {
415
416
        $distIntermediate = sin(deg2rad($point1['lat'])) * sin(deg2rad($profile1['lat'])) +
417
            cos(deg2rad($point1['lat'])) * cos(deg2rad($profile1['lat'])) * cos(deg2rad($point1['lon'] - $profile1['lon']));
418
        $dist = rad2deg(acos($distIntermediate)) * 60 * 1.1852;
419
        return(round($dist));
420
    }
421
}
422