Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Profile 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
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 Profile, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 48 | class Profile extends EntityWithDBProperties { |
||
| 49 | |||
| 50 | /** |
||
| 51 | * This array holds the supported EAP types (in "array" OUTER/INNER representation). They are not synced against the DB after instantiation. |
||
| 52 | * |
||
| 53 | * @var array |
||
| 54 | */ |
||
| 55 | private $privEaptypes; |
||
| 56 | |||
| 57 | /** |
||
| 58 | * This array holds all attributes which are defined on device level only |
||
| 59 | * |
||
| 60 | * @var array |
||
| 61 | */ |
||
| 62 | private $deviceLevelAttributes; |
||
| 63 | |||
| 64 | /** |
||
| 65 | * This array holds all attributes which are defined on device level only |
||
| 66 | * |
||
| 67 | * @var array |
||
| 68 | */ |
||
| 69 | private $eapLevelAttributes; |
||
| 70 | |||
| 71 | /** |
||
| 72 | * Class constructor for existing profiles (use IdP::newProfile() to actually create one). Retrieves all attributes and |
||
| 73 | * supported EAP types from the DB and stores them in the priv_ arrays. |
||
| 74 | * |
||
| 75 | * @param int $profileId identifier of the profile in the DB |
||
| 76 | * @param IdP $idpObject optionally, the institution to which this Profile belongs. Saves the construction of the IdP instance. If omitted, an extra query and instantiation is executed to find out. |
||
| 77 | */ |
||
| 78 | public function __construct($profileId, $idpObject = 0) { |
||
| 79 | debug(3, "--- BEGIN Constructing new Profile object ... ---\n"); |
||
| 80 | |||
| 81 | $this->databaseType = "INST"; |
||
| 82 | $this->entityOptionTable = "profile_option"; |
||
| 83 | $this->entityIdColumn = "profile_id"; |
||
| 84 | $this->identifier = $profileId; |
||
| 85 | $this->attributes = []; |
||
| 86 | |||
| 87 | $profile = DBConnection::exec($this->databaseType, "SELECT inst_id, realm, use_anon_outer, checkuser_outer, checkuser_value, verify_userinput_suffix as verify, hint_userinput_suffix as hint FROM profile WHERE profile_id = $profileId"); |
||
| 88 | debug(4, $profile); |
||
| 89 | if (!$profile || $profile->num_rows == 0) { |
||
| 90 | debug(2, "Profile $profileId not found in database!\n"); |
||
| 91 | throw new Exception("Profile $profileId not found in database!"); |
||
| 92 | } |
||
| 93 | $profileQuery = mysqli_fetch_object($profile); |
||
| 94 | if (!($idpObject instanceof IdP)) { |
||
| 95 | $this->institution = $profileQuery->inst_id; |
||
| 96 | $idp = new IdP($this->institution); |
||
| 97 | } else { |
||
| 98 | $idp = $idpObject; |
||
| 99 | $this->institution = $idp->name; |
||
| 100 | } |
||
| 101 | |||
| 102 | $optioninstance = Options::instance(); |
||
| 103 | |||
| 104 | $this->realm = $profileQuery->realm; |
||
| 105 | $this->use_anon_outer = $profileQuery->use_anon_outer; |
||
| 106 | $this->langIndex = CAT::get_lang(); |
||
| 107 | $this->inst_name = $idp->name; |
||
| 108 | |||
| 109 | $this->checkuser_outer = $profileQuery->checkuser_outer; |
||
| 110 | $this->checkuser_value = $profileQuery->checkuser_value; |
||
| 111 | $this->verify = $profileQuery->verify; |
||
| 112 | $this->hint = $profileQuery->hint; |
||
| 113 | |||
| 114 | // fetch all atributes from this profile from DB |
||
| 115 | |||
| 116 | $this->deviceLevelAttributes = $this->fetchDeviceOrEAPLevelAttributes("DEVICES"); |
||
| 117 | |||
| 118 | $this->eapLevelAttributes = $this->fetchDeviceOrEAPLevelAttributes("EAPMETHODS"); |
||
| 119 | |||
| 120 | $tempArrayProfileLevelOnly = $this->retrieveOptionsFromDatabase("SELECT DISTINCT option_name,option_value, row |
||
| 121 | FROM $this->entityOptionTable |
||
| 122 | WHERE $this->entityIdColumn = $this->identifier |
||
| 123 | AND device_id = NULL AND eap_method_id = 0 |
||
| 124 | ORDER BY option_name", "Profile"); |
||
| 125 | |||
| 126 | // add internal attributes |
||
| 127 | // they share many attribute properties, so condense the generation |
||
| 128 | |||
| 129 | $localValueIfAny = (preg_match('/@/', $this->realm) ? substr($this->realm, 0, strpos($this->realm, '@')) : "anonymous" ); |
||
| 130 | |||
| 131 | $internalAttributes = [ |
||
| 132 | "internal:profile_count" => $idp->profileCount(), |
||
| 133 | "internal:checkuser_outer" => $this->checkuser_outer, |
||
| 134 | "internal:checkuser_value" => $this->checkuser_value, |
||
| 135 | "internal:verify_userinput_suffix" => $this->verify, |
||
| 136 | "internal:hint_userinput_suffix" => $this->hint, |
||
| 137 | "internal:realm" => preg_replace('/^.*@/', '', $this->realm), |
||
| 138 | "internal:use_anon_outer" => $this->use_anon_outer, |
||
| 139 | "internal:anon_local_value" => $localValueIfAny, |
||
| 140 | ]; |
||
| 141 | |||
| 142 | foreach ($internalAttributes as $attName => $attValue) { |
||
| 143 | $tempArrayProfileLevelOnly[] = ["name" => $attName, |
||
| 144 | "value" => $attValue, |
||
| 145 | "level" => "Profile", |
||
| 146 | "row" => 0, |
||
| 147 | "flag" => NULL, |
||
| 148 | "device" => NULL, |
||
| 149 | "eapmethod" => 0]; |
||
| 150 | } |
||
| 151 | |||
| 152 | // now, fetch IdP-wide attributes |
||
| 153 | |||
| 154 | $idpoptions = $idp->getAttributes(); |
||
| 155 | |||
| 156 | foreach ($idpoptions as $theAttr) { |
||
| 157 | $temparray[] = [ |
||
| 158 | "name" => $theAttr["name"], |
||
| 159 | "value" => $theAttr["value"], |
||
| 160 | "level" => $theAttr["level"], |
||
| 161 | "row" => $theAttr["row"], |
||
| 162 | "flag" => $theAttr["flag"], |
||
| 163 | "device" => NULL, |
||
| 164 | "eapmethod" => 0, |
||
| 165 | ]; |
||
| 166 | } |
||
| 167 | |||
| 168 | // add all attributes which are device or eap method specific to |
||
| 169 | // final attribute array (they cannot be overridden) |
||
| 170 | $this->attributes = array_merge($this->deviceLevelAttributes, $this->eapLevelAttributes); |
||
| 171 | |||
| 172 | // now add profile-level attributes if not already set on deeper level |
||
| 173 | |||
| 174 | foreach ($tempArrayProfileLevelOnly as $attrib) { |
||
| 175 | $ignore = ""; |
||
| 176 | foreach ($this->attributes as $approvedAttrib) { |
||
| 177 | if ($attrib["name"] == $approvedAttrib["name"] && $approvedAttrib["level"] != "Profile") { |
||
| 178 | $ignore = "YES"; |
||
| 179 | } |
||
| 180 | } |
||
| 181 | if ($ignore != "YES") { |
||
| 182 | $this->attributes[] = $attrib; |
||
| 183 | } |
||
| 184 | } |
||
| 185 | |||
| 186 | // now, add IdP-wide attribs |
||
| 187 | |||
| 188 | foreach ($idpoptions as $attrib) { |
||
| 189 | $ignore = ""; |
||
| 190 | foreach ($this->attributes as $approvedAttrib) { |
||
| 191 | if ($attrib["name"] == $approvedAttrib["name"] && $approvedAttrib["level"] != "IdP") { |
||
| 192 | $ignore = "YES"; |
||
| 193 | } |
||
| 194 | } |
||
| 195 | if ($ignore != "YES") { |
||
| 196 | $this->attributes[] = $attrib; |
||
| 197 | } |
||
| 198 | } |
||
| 199 | |||
| 200 | $this->name = getLocalisedValue($this->getAttributes('profile:name'), $this->langIndex); // cannot be set per device or eap type |
||
| 201 | |||
| 202 | $eapMethod = DBConnection::exec($this->databaseType, "SELECT eap_method_id |
||
| 203 | FROM supported_eap supp |
||
| 204 | WHERE supp.profile_id = $this->identifier |
||
| 205 | ORDER by preference"); |
||
| 206 | $eapTypeArray = []; |
||
| 207 | while ($eapQuery = (mysqli_fetch_object($eapMethod))) { |
||
| 208 | $eaptype = EAP::EAPMethodArrayFromId($eapQuery->eap_method_id); |
||
| 209 | $eapTypeArray[] = $eaptype; |
||
| 210 | } |
||
| 211 | debug(4, "Looks like this profile supports the following EAP types: "); |
||
| 212 | debug(4, $eapTypeArray); |
||
| 213 | $this->privEaptypes = $eapTypeArray; |
||
| 214 | |||
| 215 | debug(3, "--- END Constructing new Profile object ... ---\n"); |
||
| 216 | } |
||
| 217 | |||
| 218 | private function fetchDeviceOrEAPLevelAttributes($devicesOrEAPMethods) { |
||
| 219 | // only one of the two is allowed to be set |
||
| 220 | $temparray = []; |
||
| 221 | $optioninstance = Options::instance(); |
||
| 222 | switch ($devicesOrEAPMethods) { |
||
| 223 | case "DEVICES": |
||
| 224 | $queryPart = "device_id"; |
||
| 225 | $conditionPart = "AND eap_method_id = 0"; |
||
| 226 | break; |
||
| 227 | case "EAPMETHODS": |
||
| 228 | $queryPart = "eap_method_id"; |
||
| 229 | $conditionPart = "AND device_id = NULL"; |
||
| 230 | break; |
||
| 231 | } |
||
| 232 | |||
| 233 | $allAttributes = DBConnection::exec($this->databaseType, "SELECT option_name, option_value, $queryPart as deviceormethod, row |
||
| 234 | FROM $this->entityOptionTable |
||
| 235 | WHERE $this->entityIdColumn = $this->identifier $conditionPart"); |
||
| 236 | |||
| 237 | while ($attributeQuery = mysqli_fetch_object($allAttributes)) { |
||
| 238 | |||
| 239 | $optinfo = $optioninstance->optionType($attributeQuery->option_name); |
||
| 240 | if ($optinfo['type'] != "file") { |
||
| 241 | $temparray[] = [ |
||
| 242 | "name" => $attributeQuery->option_name, |
||
| 243 | "value" => $attributeQuery->option_value, |
||
| 244 | "level" => "Method", |
||
| 245 | "row" => $attributeQuery->row, |
||
| 246 | "flag" => $optinfo['flag'], |
||
| 247 | "device" => ($devicesOrEAPMethods == "DEVICES" ? $attributeQuery->deviceormethod : NULL), |
||
| 248 | "eapmethod" => ($devicesOrEAPMethods == "DEVICES" ? 0 : EAP::EAPMethodArrayFromId($attributeQuery->deviceormethod))]; |
||
| 249 | } else { |
||
| 250 | $decodedAttribute = $this->decodeFileAttribute($attributeQuery->option_value); |
||
| 251 | |||
| 252 | $temparray[] = [ |
||
| 253 | "name" => $attributeQuery->option_name, |
||
| 254 | "value" => ( $decodedAttribute['lang'] == "" ? $decodedAttribute['content'] : serialize($decodedAttribute)), |
||
| 255 | "level" => "Method", |
||
| 256 | "row" => $attributeQuery->row, |
||
| 257 | "flag" => $optinfo['flag'], |
||
| 258 | "device" => ($devicesOrEAPMethods == "DEVICES" ? $attributeQuery->deviceormethod : NULL), |
||
| 259 | "eapmethod" => ($devicesOrEAPMethods == "DEVICES" ? 0 : EAP::EAPMethodArrayFromId($attributeQuery->deviceormethod))]; |
||
| 260 | } |
||
| 261 | } |
||
| 262 | return $temparray; |
||
| 263 | } |
||
| 264 | |||
| 265 | /** |
||
| 266 | * find a profile, given its realm |
||
| 267 | */ |
||
| 268 | public static function profileFromRealm($realm) { |
||
| 269 | $execQuery = DBConnection::exec($this->databaseType, "SELECT profile_id FROM profile WHERE realm LIKE '%@$realm'"); |
||
| 270 | if ($profileIdQuery = mysqli_fetch_object($execQuery)) { |
||
| 271 | return $profileIdQuery->profile_id; |
||
| 272 | } |
||
| 273 | return FALSE; |
||
| 274 | } |
||
| 275 | |||
| 276 | /** |
||
| 277 | * update the last_changed timestamp for this profile |
||
| 278 | */ |
||
| 279 | public function updateFreshness() { |
||
| 280 | DBConnection::exec($this->databaseType, "UPDATE profile SET last_change = CURRENT_TIMESTAMP WHERE profile_id = $this->identifier"); |
||
| 281 | } |
||
| 282 | |||
| 283 | /** |
||
| 284 | * gets the last-modified timestamp (useful for caching "dirty" check) |
||
| 285 | */ |
||
| 286 | public function getFreshness() { |
||
| 287 | $execUpdate = DBConnection::exec($this->databaseType, "SELECT last_change FROM profile WHERE profile_id = $this->identifier"); |
||
| 288 | if ($freshnessQuery = mysqli_fetch_object($execUpdate)) { |
||
| 289 | return $freshnessQuery->last_change; |
||
| 290 | } |
||
| 291 | } |
||
| 292 | |||
| 293 | /** |
||
| 294 | * tests if the configurator needs to be regenerated |
||
| 295 | * returns the configurator path or NULL if regeneration is required |
||
| 296 | */ |
||
| 297 | |||
| 298 | /** |
||
| 299 | * This function tests if the configurator needs to be regenerated (properties of the Profile may have changed since the last configurator generation). |
||
| 300 | * |
||
| 301 | * @param string $device device ID to check |
||
| 302 | * @return mixed a string with the path to the configurator download, or NULL if it needs to be regenerated |
||
| 303 | */ |
||
| 304 | public function testCache($device) { |
||
| 305 | $returnValue = NULL; |
||
| 306 | $escapedDevice = DBConnection::escape_value($this->databaseType, $device); |
||
| 307 | $result = DBConnection::exec($this->databaseType, "SELECT download_path, mime, UNIX_TIMESTAMP(installer_time) AS tm FROM downloads WHERE profile_id = $this->identifier AND device_id = '$escapedDevice' AND lang = '$this->langIndex'"); |
||
| 308 | if ($result && $cache = mysqli_fetch_object($result)) { |
||
| 309 | $execUpdate = DBConnection::exec($this->databaseType, "SELECT UNIX_TIMESTAMP(last_change) AS last_change FROM profile WHERE profile_id = $this->identifier"); |
||
| 310 | if ($lastChange = mysqli_fetch_object($execUpdate)->last_change) { |
||
| 311 | if ($lastChange < $cache->tm) { |
||
| 312 | debug(4, "Installer cached:$cache->download_path\n"); |
||
| 313 | $returnValue = ['cache' => $cache->download_path, 'mime' => $cache->mime]; |
||
| 314 | } |
||
| 315 | } |
||
| 316 | } |
||
| 317 | return $returnValue; |
||
| 318 | } |
||
| 319 | |||
| 320 | /** |
||
| 321 | * Updates database with new installler location |
||
| 322 | * |
||
| 323 | * @param string device the device identifier string |
||
| 324 | * @param string path the path where the new installer can be found |
||
| 325 | */ |
||
| 326 | public function updateCache($device, $path, $mime) { |
||
| 327 | $escapedDevice = DBConnection::escape_value($this->databaseType, $device); |
||
| 328 | $escapedPath = DBConnection::escape_value($this->databaseType, $path); |
||
| 329 | DBConnection::exec($this->databaseType, "INSERT INTO downloads (profile_id,device_id,download_path,mime,lang,installer_time) |
||
| 330 | VALUES ($this->identifier, '$escapedDevice', '$escapedPath', '$mime', '$this->langIndex', CURRENT_TIMESTAMP ) |
||
| 331 | ON DUPLICATE KEY UPDATE download_path = '$escapedPath', mime = '$mime', installer_time = CURRENT_TIMESTAMP"); |
||
| 332 | } |
||
| 333 | |||
| 334 | /** |
||
| 335 | * Log a new download for our stats |
||
| 336 | * |
||
| 337 | * @param device the device id string |
||
| 338 | * @param area either admin or user |
||
| 339 | * @return TRUE if incrementing worked, FALSE if not |
||
| 340 | */ |
||
| 341 | public function incrementDownloadStats($device, $area) { |
||
| 342 | $escapedDevice = DBConnection::escape_value($this->databaseType, $device); |
||
| 343 | if ($area == "admin" || $area == "user") { |
||
| 344 | DBConnection::exec($this->databaseType, "INSERT INTO downloads (profile_id, device_id, lang, downloads_$area) VALUES ($this->identifier, '$escapedDevice','$this->langIndex', 1) ON DUPLICATE KEY UPDATE downloads_$area = downloads_$area + 1"); |
||
| 345 | return TRUE; |
||
| 346 | } |
||
| 347 | return FALSE; |
||
| 348 | } |
||
| 349 | |||
| 350 | /** |
||
| 351 | * Retrieve current download stats from database, either for one specific device or for all devices |
||
| 352 | * @param string $device the device id string |
||
| 353 | * @return mixed user downloads of this profile; if device is given, returns the counter as int, otherwise an array with devicename => counter |
||
| 354 | */ |
||
| 355 | public function getUserDownloadStats($device = 0) { |
||
| 356 | $returnarray = []; |
||
| 357 | $numbers = DBConnection::exec($this->databaseType, "SELECT device_id, SUM(downloads_user) AS downloads_user FROM downloads WHERE profile_id = $this->identifier GROUP BY device_id"); |
||
| 358 | while ($statsQuery = mysqli_fetch_object($numbers)) { |
||
| 359 | $returnarray[$statsQuery->device_id] = $statsQuery->downloads_user; |
||
| 360 | } |
||
| 361 | if ($device !== 0) { |
||
| 362 | if (isset($returnarray[$device])) { |
||
| 363 | return $returnarray[$device]; |
||
| 364 | } |
||
| 365 | return 0; |
||
| 366 | } |
||
| 367 | // we should pretty-print the device names |
||
| 368 | $finalarray = []; |
||
| 369 | $devlist = Devices::listDevices(); |
||
| 370 | foreach ($returnarray as $devId => $count) { |
||
| 371 | if (isset($devlist[$devId])) { |
||
| 372 | $finalarray[$devlist[$devId]['display']] = $count; |
||
| 373 | } |
||
| 374 | } |
||
| 375 | return $finalarray; |
||
| 376 | } |
||
| 377 | |||
| 378 | /** |
||
| 379 | * adds an attribute to this profile; not the usual function from EntityWithDBProperties |
||
| 380 | * because this class also has per-EAP-type and per-device sub-settings |
||
| 381 | * |
||
| 382 | * @param string $attrName name of the attribute to set |
||
| 383 | * @param string $attrValue value of the attribute to set |
||
| 384 | * @param int $eapType identifier of the EAP type in the database. 0 if the attribute is valid for all EAP types. |
||
| 385 | * @param string $device identifier of the device in the databse. Omit the argument if attribute is valid for all devices. |
||
| 386 | */ |
||
| 387 | private function addAttributeAllLevels($attrName, $attrValue, $eapType, $device = 0) { |
||
| 388 | $escapedAttrName = DBConnection::escape_value($this->databaseType, $attrName); |
||
| 389 | $escapedAttrValue = DBConnection::escape_value($this->databaseType, $attrValue); |
||
| 390 | |||
| 391 | DBConnection::exec($this->databaseType, "INSERT INTO $this->entityOptionTable ($this->entityIdColumn, option_name, option_value, eap_method_id" . ($device !== 0 ? ",device_id" : "") . ") |
||
| 392 | VALUES(" . $this->identifier . ", '$escapedAttrName', '$escapedAttrValue', $eapType" . ($device !== 0 ? ",'" . DBConnection::escape_value($this->databaseType, $device) . "'" : "" ) . ")"); |
||
| 393 | $this->updateFreshness(); |
||
| 394 | } |
||
| 395 | |||
| 396 | public function addAttributeEAPSpecific($attrName, $attrValue, $eapType) { |
||
| 397 | $this->addAttributeAllLevels($attrName, $attrValue, $eapType, 0); |
||
| 398 | } |
||
| 399 | |||
| 400 | public function addAttributeDeviceSpecific($attrName, $attrValue, $device) { |
||
| 401 | $this->addAttributeAllLevels($attrName, $attrValue, 0, $device); |
||
| 402 | } |
||
| 403 | |||
| 404 | public function addAttribute($attrName, $attrValue) { |
||
| 405 | $this->addAttributeAllLevels($attrName, $attrValue, 0, 0); |
||
| 406 | } |
||
| 407 | |||
| 408 | /** |
||
| 409 | * register new supported EAP method for this profile |
||
| 410 | * |
||
| 411 | * @param array $type The EAP Type, as defined in class EAP |
||
| 412 | * @param int $preference preference of this EAP Type. If a preference value is re-used, the order of EAP types of the same preference level is undefined. |
||
| 413 | * |
||
| 414 | */ |
||
| 415 | public function addSupportedEapMethod($type, $preference) { |
||
| 416 | DBConnection::exec($this->databaseType, "INSERT INTO supported_eap (profile_id, eap_method_id, preference) VALUES (" |
||
| 417 | . $this->identifier . ", " |
||
| 418 | . EAP::EAPMethodIdFromArray($type) . ", " |
||
| 419 | . $preference . ")"); |
||
| 420 | $this->updateFreshness(); |
||
| 421 | } |
||
| 422 | |||
| 423 | /** |
||
| 424 | * Deletes the profile from database and uninstantiates itself. |
||
| 425 | * |
||
| 426 | */ |
||
| 427 | public function destroy() { |
||
| 428 | DBConnection::exec($this->databaseType, "DELETE FROM profile_option WHERE profile_id = $this->identifier"); |
||
| 429 | DBConnection::exec($this->databaseType, "DELETE FROM supported_eap WHERE profile_id = $this->identifier"); |
||
| 430 | DBConnection::exec($this->databaseType, "DELETE FROM profile WHERE profile_id = $this->identifier"); |
||
| 431 | unset($this); |
||
| 432 | } |
||
| 433 | |||
| 434 | /** |
||
| 435 | * Removes all supported EAP methods |
||
| 436 | */ |
||
| 437 | public function flushSupportedEapMethods() { |
||
| 438 | DBConnection::exec($this->databaseType, "DELETE FROM supported_eap WHERE profile_id = $this->identifier"); |
||
| 439 | $this->updateFreshness(); |
||
| 440 | } |
||
| 441 | |||
| 442 | /** Toggle anonymous outer ID support. |
||
| 443 | * |
||
| 444 | * @param boolean $shallwe TRUE to enable outer identities (needs valid $realm), FALSE to disable |
||
| 445 | * |
||
| 446 | */ |
||
| 447 | public function setAnonymousIDSupport($shallwe) { |
||
| 448 | DBConnection::exec($this->databaseType, "UPDATE profile SET use_anon_outer = " . ($shallwe == true ? "1" : "0") . " WHERE profile_id = $this->identifier"); |
||
| 449 | } |
||
| 450 | |||
| 451 | /** Toggle special username for realm checks |
||
| 452 | * |
||
| 453 | * @param boolean $shallwe TRUE to enable outer identities (needs valid $realm), FALSE to disable |
||
| 454 | * @param string $localpart the username |
||
| 455 | * |
||
| 456 | */ |
||
| 457 | public function setRealmCheckUser($shallwe, $localpart = NULL) { |
||
| 458 | DBConnection::exec($this->databaseType, "UPDATE profile SET checkuser_outer = " . ($shallwe == true ? "1" : "0") . |
||
| 459 | ( $localpart !== NULL ? ", checkuser_value = '$localpart' " : "") . |
||
| 460 | " WHERE profile_id = $this->identifier"); |
||
| 461 | } |
||
| 462 | |||
| 463 | /** should username be verified or even prefilled? |
||
| 464 | * |
||
| 465 | */ |
||
| 466 | public function setInputVerificationPreference($verify, $hint) { |
||
| 467 | DBConnection::exec($this->databaseType, "UPDATE profile SET verify_userinput_suffix = " . ($verify == true ? "1" : "0") . |
||
| 468 | ", hint_userinput_suffix = " . ($hint == true ? "1" : "0") . |
||
| 469 | " WHERE profile_id = $this->identifier"); |
||
| 470 | } |
||
| 471 | |||
| 472 | /** |
||
| 473 | * Specifies the realm of this profile. |
||
| 474 | * |
||
| 475 | * @param string $realm the realm (potentially with the local@ part that should be used for anonymous identities) |
||
| 476 | */ |
||
| 477 | public function setRealm($realm) { |
||
| 478 | $escapedRealm = DBConnection::escape_value($this->databaseType, $realm); |
||
| 479 | DBConnection::exec($this->databaseType, "UPDATE profile SET realm = '$escapedRealm' WHERE profile_id = $this->identifier"); |
||
| 480 | $this->realm = $escapedRealm; |
||
| 481 | } |
||
| 482 | |||
| 483 | /** |
||
| 484 | * Produces an array of EAP methods supported by this profile, ordered by preference |
||
| 485 | * |
||
| 486 | * @param int $completeOnly if set and non-zero limits the output to methods with complete information |
||
| 487 | * @return array list of EAP methods, (in "array" OUTER/INNER representation) |
||
| 488 | */ |
||
| 489 | public function getEapMethodsinOrderOfPreference($completeOnly = 0) { |
||
| 490 | $temparray = []; |
||
| 491 | |||
| 492 | if ($completeOnly == 0) { |
||
| 493 | return $this->privEaptypes; |
||
| 494 | } else { |
||
| 495 | foreach ($this->privEaptypes as $type) { |
||
| 496 | if ($this->isEapTypeDefinitionComplete($type) === true) { |
||
| 497 | $temparray[] = $type; |
||
| 498 | } |
||
| 499 | } |
||
| 500 | return($temparray); |
||
| 501 | } |
||
| 502 | } |
||
| 503 | |||
| 504 | /** |
||
| 505 | * Performs a sanity check for a given EAP type - did the admin submit enough information to create installers for him? |
||
| 506 | * |
||
| 507 | * @param array $eaptype the EAP type in "array" OUTER/INNER representation |
||
| 508 | * @return mixed TRUE if the EAP type is complete; an array of missing attribues if it's incomplete; FALSE if it's incomplete for other reasons |
||
| 509 | */ |
||
| 510 | public function isEapTypeDefinitionComplete($eaptype) { |
||
| 511 | $missing = []; |
||
| 512 | // TLS, TTLS, PEAP outer phase need a CA certficate and a Server Name |
||
| 513 | if ($eaptype["OUTER"] == PEAP || $eaptype["OUTER"] == TLS || $eaptype["OUTER"] == TTLS || $eaptype["OUTER"] == FAST) { |
||
| 514 | |||
| 515 | $cnOption = $this->getAttributes("eap:server_name"); // cannot be set per device or eap type |
||
| 516 | $caOption = $this->getAttributes("eap:ca_file"); // cannot be set per device or eap type |
||
| 517 | |||
| 518 | if (count($caOption) > 0 && count($cnOption) > 0) {// see if we have at least one root CA cert |
||
| 519 | foreach ($caOption as $oneCa) { |
||
| 520 | $x509 = new X509(); |
||
| 521 | $caParsed = $x509->processCertificate($oneCa['value']); |
||
| 522 | if ($caParsed['root'] == 1) { |
||
| 523 | return true; |
||
| 524 | } |
||
| 525 | } |
||
| 526 | $missing[] = "eap:ca_file"; |
||
| 527 | } |
||
| 528 | if (count($caOption) == 0) { |
||
| 529 | $missing[] = "eap:ca_file"; |
||
| 530 | } |
||
| 531 | if (count($cnOption) == 0) { |
||
| 532 | $missing[] = "eap:server_name"; |
||
| 533 | } |
||
| 534 | return $missing; |
||
| 535 | } elseif ($eaptype["OUTER"] == PWD || $eaptype["INNER"] == NE_SILVERBULLET) { |
||
| 536 | /* |
||
|
|
|||
| 537 | $cn_option = $this->getAttributes("eap:server_name", $eaptype); |
||
| 538 | if (count($cn_option) > 0) */ |
||
| 539 | return true; |
||
| 540 | /* $missing[] = "eap:server_name"; |
||
| 541 | return $missing; */ |
||
| 542 | } |
||
| 543 | |||
| 544 | // we have no idea; let's say false |
||
| 545 | |||
| 546 | return false; |
||
| 547 | } |
||
| 548 | |||
| 549 | /** |
||
| 550 | * list all devices marking their availabiblity and possible redirects |
||
| 551 | * |
||
| 552 | * @param string $locale for text-based attributes, either returns values for the default value, or if specified here, in the locale specified |
||
| 553 | * @return array of device ids display names and their status |
||
| 554 | */ |
||
| 555 | public function listDevices($locale = 0) { |
||
| 556 | if ($locale == 0) { |
||
| 557 | $locale = $this->langIndex; |
||
| 558 | } |
||
| 559 | $redirectUrl = 0; |
||
| 560 | $returnarray = []; |
||
| 561 | $redirect = $this->getAttributes("device-specific:redirect"); // this might return per-device ones or the general one |
||
| 562 | // if it was a general one, we are done. Find out if there is one such |
||
| 563 | // which has device = NULL |
||
| 564 | $generalRedirect = NULL; |
||
| 565 | foreach ($redirect as $index => $oneRedirect) { |
||
| 566 | if ($oneRedirect["level"] == "Profile") { |
||
| 567 | $generalRedirect = $index; |
||
| 568 | } |
||
| 569 | } |
||
| 570 | if ($generalRedirect !== NULL) { // could be index 0 |
||
| 571 | $unserialised = unserialize($redirect[$generalRedirect]['value']); |
||
| 572 | return [['id' => '0', 'redirect' => $unserialised['content']]]; |
||
| 573 | } |
||
| 574 | $preferredEap = $this->getEapMethodsinOrderOfPreference(1); |
||
| 575 | $eAPOptions = []; |
||
| 576 | foreach (Devices::listDevices() as $deviceIndex => $deviceProperties) { |
||
| 577 | $factory = new DeviceFactory($deviceIndex); |
||
| 578 | $dev = $factory->device; |
||
| 579 | // find the attribute pertaining to the specific device |
||
| 580 | $redirectUrl = 0; |
||
| 581 | foreach ($redirect as $index => $oneRedirect) { |
||
| 582 | if ($oneRedirect["device"] == $deviceIndex) { |
||
| 583 | $redirectUrl = getLocalisedValue($oneRedirect, $locale); |
||
| 584 | } |
||
| 585 | } |
||
| 586 | $devStatus = AVAILABLE; |
||
| 587 | $message = 0; |
||
| 588 | if (isset($deviceProperties['options']) && isset($deviceProperties['options']['message']) && $deviceProperties['options']['message']) { |
||
| 589 | $message = $deviceProperties['options']['message']; |
||
| 590 | } |
||
| 591 | |||
| 592 | if ($redirectUrl === 0) { |
||
| 593 | $eapCustomtext = ""; |
||
| 594 | $deviceCustomtext = ""; |
||
| 595 | if (isset($deviceProperties['options']) && isset($deviceProperties['options']['redirect']) && $deviceProperties['options']['redirect']) { |
||
| 596 | $devStatus = HIDDEN; |
||
| 597 | } else { |
||
| 598 | $eap = $dev->getPreferredEapType($preferredEap); |
||
| 599 | if ($eap) { |
||
| 600 | if (isset($eAPOptions["eap-specific:customtext"][serialize($eap)])) { |
||
| 601 | $eapCustomtext = $eAPOptions["eap-specific:customtext"][serialize($eap)]; |
||
| 602 | } else { |
||
| 603 | // fetch customtexts from method-level attributes |
||
| 604 | $eapCustomtext = ""; |
||
| 605 | $customTextAttributes = []; |
||
| 606 | $attributeList = $this->getAttributes("eap-specific:redirect"); |
||
| 607 | foreach ($attributeList as $oneAttribute) { |
||
| 608 | if ($oneAttribute["eapmethod"] == $eap) { |
||
| 609 | $customTextAttributes[] = $oneAttribute; |
||
| 610 | } |
||
| 611 | } |
||
| 612 | if (count($customTextAttributes) > 0) { |
||
| 613 | $eapCustomtext = getLocalisedValue($customTextAttributes, $locale); |
||
| 614 | } |
||
| 615 | $eAPOptions["eap-specific:customtext"][serialize($eap)] = $eapCustomtext; |
||
| 616 | } |
||
| 617 | // fetch customtexts for device |
||
| 618 | $deviceCustomtext = ""; |
||
| 619 | $customTextAttributes = []; |
||
| 620 | $attributeList = $this->getAttributes("device-specific:redirect"); |
||
| 621 | foreach ($attributeList as $oneAttribute) { |
||
| 622 | if ($oneAttribute["device"] == $deviceIndex) { |
||
| 623 | $customTextAttributes[] = $oneAttribute; |
||
| 624 | } |
||
| 625 | } |
||
| 626 | $deviceCustomtext = getLocalisedValue($customTextAttributes, $locale); |
||
| 627 | } else { |
||
| 628 | $devStatus = UNAVAILABLE; |
||
| 629 | } |
||
| 630 | } |
||
| 631 | } |
||
| 632 | $returnarray[] = ['id' => $deviceIndex, 'display' => $deviceProperties['display'], 'status' => $devStatus, 'redirect' => $redirectUrl, 'eap_customtext' => $eapCustomtext, 'device_customtext' => $deviceCustomtext, 'message' => $message, 'options' => $deviceProperties['options']]; |
||
| 633 | } |
||
| 634 | return $returnarray; |
||
| 635 | } |
||
| 636 | |||
| 637 | /** |
||
| 638 | * prepare profile attributes for device modules |
||
| 639 | * Gets profile attributes taking into account the most specific level on which they may be defined |
||
| 640 | * as wel as the chosen language. |
||
| 641 | * can be called with an optional $eap argument |
||
| 642 | * |
||
| 643 | * @param array $eap if specified, retrieves attributes specific to the given EAP type |
||
| 644 | * @return array list of attributes in collapsed style (index is the attrib name, value is an array of different values) |
||
| 645 | */ |
||
| 646 | public function getCollapsedAttributes($eap = 0) { |
||
| 647 | $attrBefore = $this->getAttributes(); |
||
| 648 | $attr = []; |
||
| 649 | if ($eap != 0) { // filter out attributes pertaining only to a certain EAP type |
||
| 650 | foreach ($attrBefore as $index => $attrib) { |
||
| 651 | if ($attrib['eapmethod'] == $eap || $attrib['eapmethod'] == 0) { |
||
| 652 | $attr[] = $attrib; |
||
| 653 | } |
||
| 654 | } |
||
| 655 | } |
||
| 656 | $temp1 = []; |
||
| 657 | foreach ($attr as $b) { |
||
| 658 | $name = $b['name']; |
||
| 659 | $temp1[] = $name; |
||
| 660 | $level = $b['level']; |
||
| 661 | // $S[$l] = $z[$l]; |
||
| 662 | $value = $b['value']; |
||
| 663 | if (!isset($temp[$name][$level])) { |
||
| 664 | $temp[$name][$level] = []; |
||
| 665 | } |
||
| 666 | if ($b['flag'] == 'ML') { |
||
| 667 | $v = unserialize($value); |
||
| 668 | $value = [$v['lang'] => $v['content']]; |
||
| 669 | } |
||
| 670 | $temp[$name][$level][] = $value; |
||
| 671 | $flags[$name] = $b['flag']; |
||
| 672 | } |
||
| 673 | foreach ($temp1 as $name) { |
||
| 674 | if ($flags[$name] == 'ML') { |
||
| 675 | $S = []; |
||
| 676 | if (isset($temp[$name]['Profile'])) { |
||
| 677 | foreach ($temp[$name]['Profile'] as $z) { |
||
| 678 | foreach ($z as $l => $w) { |
||
| 679 | $S[$l] = $w; |
||
| 680 | } |
||
| 681 | } |
||
| 682 | } |
||
| 683 | if (!$S && isset($temp[$name]['IdP'])) { |
||
| 684 | foreach ($temp[$name]['IdP'] as $z) { |
||
| 685 | foreach ($z as $l => $w) { |
||
| 686 | $S[$l] = $w; |
||
| 687 | } |
||
| 688 | } |
||
| 689 | } |
||
| 690 | $out[$name]['langs'] = $S; |
||
| 691 | <<<<<<< HEAD |
||
| 692 | if (isset($S[$this->lang_index]) || isset($S['C'])) |
||
| 693 | $out[$name][0] = (isset($S[$this->lang_index])) ? $S[$this->lang_index] : $S['C']; |
||
| 694 | if (isset($S['en'])) |
||
| 695 | $out[$name][1] = $S['en']; |
||
| 696 | elseif(isset($S['C'])) |
||
| 697 | $out[$name][1] = $S['C']; |
||
| 698 | else |
||
| 699 | $out[$name][1] = $out[$name][0]; |
||
| 700 | ======= |
||
| 701 | if (isset($S[$this->langIndex]) || isset($S['C'])) { |
||
| 702 | $out[$name][0] = (isset($S[$this->langIndex])) ? $S[$this->langIndex] : $S['C']; |
||
| 703 | } |
||
| 704 | >>>>>>> cc9aa934406bee74d39aa1556cebec9586f485e6 |
||
| 705 | } else { |
||
| 706 | if (isset($temp[$name]['Method'])) { |
||
| 707 | $out[$name] = $temp[$name]['Method']; |
||
| 708 | } elseif (isset($temp[$name]['Profile'])) { |
||
| 709 | $out[$name] = $temp[$name]['Profile']; |
||
| 710 | } else { |
||
| 711 | $out[$name] = $temp[$name]['IdP']; |
||
| 712 | } |
||
| 713 | } |
||
| 714 | } |
||
| 715 | return($out); |
||
| 716 | } |
||
| 717 | |||
| 718 | /** |
||
| 719 | * |
||
| 720 | */ |
||
| 721 | public function getSufficientConfig() { |
||
| 722 | $result = DBConnection::exec($this->databaseType, "SELECT sufficient_config FROM profile WHERE profile_id = " . $this->identifier); |
||
| 723 | $configQuery = mysqli_fetch_row($result); |
||
| 724 | if ($configQuery[0] == "0") { |
||
| 725 | return FALSE; |
||
| 726 | } |
||
| 727 | return TRUE; |
||
| 728 | } |
||
| 729 | |||
| 730 | /** |
||
| 731 | * Checks if the profile has enough information to have something to show to end users. This does not necessarily mean |
||
| 732 | * that there's a fully configured EAP type - it is sufficient if a redirect has been set for at least one device. |
||
| 733 | * |
||
| 734 | * @return boolean TRUE if enough information for showtime is set; FALSE if not |
||
| 735 | */ |
||
| 736 | public function readyForShowtime() { |
||
| 737 | $properConfig = FALSE; |
||
| 738 | $attribs = $this->getCollapsedAttributes(); |
||
| 739 | // do we have enough to go live? Check if any of the configured EAP methods is completely configured ... |
||
| 740 | if (sizeof($this->getEapMethodsinOrderOfPreference(1)) > 0) { |
||
| 741 | $properConfig = TRUE; |
||
| 742 | } |
||
| 743 | // if not, it could still be that general redirect has been set |
||
| 744 | if (!$properConfig) { |
||
| 745 | if (isset($attribs['device-specific:redirect'])) { |
||
| 746 | $properConfig = TRUE; |
||
| 747 | } |
||
| 748 | // TODO: or maybe just a per-device redirect? would be good enough... |
||
| 749 | } |
||
| 750 | // do we know at least one SSID to configure, or work with wired? If not, it's not ready... |
||
| 751 | if (!isset($attribs['media:SSID']) && |
||
| 752 | !isset($attribs['media:SSID_with_legacy']) && |
||
| 753 | (!isset(Config::$CONSORTIUM['ssid']) || count(Config::$CONSORTIUM['ssid']) == 0) && |
||
| 754 | !isset($attribs['media:wired'])) { |
||
| 755 | $properConfig = FALSE; |
||
| 756 | } |
||
| 757 | return $properConfig; |
||
| 758 | } |
||
| 759 | |||
| 760 | /** |
||
| 761 | * set the showtime and QR-user attributes if prepShowTime says that there is enough info *and* the admin flagged the profile for showing |
||
| 762 | */ |
||
| 763 | public function prepShowtime() { |
||
| 764 | $properConfig = $this->readyForShowtime(); |
||
| 765 | if ($properConfig) { |
||
| 766 | DBConnection::exec($this->databaseType, "UPDATE profile SET sufficient_config = TRUE WHERE profile_id = " . $this->identifier); |
||
| 767 | } else { |
||
| 768 | DBConnection::exec($this->databaseType, "UPDATE profile SET sufficient_config = FALSE WHERE profile_id = " . $this->identifier); |
||
| 769 | } |
||
| 770 | $attribs = $this->getCollapsedAttributes(); |
||
| 771 | // if not enough info to go live, set FALSE |
||
| 772 | // even if enough info is there, admin has the ultimate say: |
||
| 773 | // if he doesn't want to go live, no further checks are needed, set FALSE as well |
||
| 774 | if (!$properConfig || !isset($attribs['profile:production']) || (isset($attribs['profile:production']) && $attribs['profile:production'][0] != "on")) { |
||
| 775 | DBConnection::exec($this->databaseType, "UPDATE profile SET showtime = FALSE WHERE profile_id = " . $this->identifier); |
||
| 776 | return; |
||
| 777 | } |
||
| 778 | DBConnection::exec($this->databaseType, "UPDATE profile SET showtime = TRUE WHERE profile_id = " . $this->identifier); |
||
| 779 | } |
||
| 780 | |||
| 781 | /** |
||
| 782 | * Checks if the profile is shown (showable) to end users |
||
| 783 | * @return boolean TRUE if profile is shown; FALSE if not |
||
| 784 | */ |
||
| 825 |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.