| Total Complexity | 55 |
| Total Lines | 438 |
| Duplicated Lines | 0 % |
| Changes | 4 | ||
| Bugs | 0 | Features | 0 |
Complex classes like AccountSearchService 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 AccountSearchService, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 57 | final class AccountSearchService extends Service |
||
| 58 | { |
||
| 59 | /** |
||
| 60 | * Regex filters for special searching |
||
| 61 | */ |
||
| 62 | const FILTERS = [ |
||
| 63 | 'condition' => [ |
||
| 64 | 'subject' => ['is', 'not'], |
||
| 65 | 'condition' => ['expired', 'private'] |
||
| 66 | ], |
||
| 67 | 'items' => [ |
||
| 68 | 'subject' => ['id', 'user', 'group', 'file', 'owner', 'maingroup', 'client', 'category', 'name_regex'], |
||
| 69 | 'condition' => null |
||
| 70 | ], |
||
| 71 | 'operator' => [ |
||
| 72 | 'subject' => ['op'], |
||
| 73 | 'condition' => ['and', 'or'] |
||
| 74 | ] |
||
| 75 | ]; |
||
| 76 | |||
| 77 | const COLORS_CACHE_FILE = CACHE_PATH . DIRECTORY_SEPARATOR . 'colors.cache'; |
||
| 78 | |||
| 79 | /** |
||
| 80 | * Cache expire time |
||
| 81 | */ |
||
| 82 | const CACHE_EXPIRE = 86400; |
||
| 83 | |||
| 84 | /** |
||
| 85 | * Colores para resaltar las cuentas |
||
| 86 | */ |
||
| 87 | const COLORS = [ |
||
| 88 | '2196F3', |
||
| 89 | '03A9F4', |
||
| 90 | '00BCD4', |
||
| 91 | '009688', |
||
| 92 | '4CAF50', |
||
| 93 | '8BC34A', |
||
| 94 | 'CDDC39', |
||
| 95 | 'FFC107', |
||
| 96 | '795548', |
||
| 97 | '607D8B', |
||
| 98 | '9E9E9E', |
||
| 99 | 'FF5722', |
||
| 100 | 'F44336', |
||
| 101 | 'E91E63', |
||
| 102 | '9C27B0', |
||
| 103 | '673AB7', |
||
| 104 | '3F51B5', |
||
| 105 | ]; |
||
| 106 | /** |
||
| 107 | * @var AccountFilterUser |
||
| 108 | */ |
||
| 109 | private $accountFilterUser; |
||
| 110 | /** |
||
| 111 | * @var AccountAclService |
||
| 112 | */ |
||
| 113 | private $accountAclService; |
||
| 114 | /** |
||
| 115 | * @var ConfigData |
||
| 116 | */ |
||
| 117 | private $configData; |
||
| 118 | /** |
||
| 119 | * @var AccountToTagRepository |
||
| 120 | */ |
||
| 121 | private $accountToTagRepository; |
||
| 122 | /** |
||
| 123 | * @var AccountToUserRepository |
||
| 124 | */ |
||
| 125 | private $accountToUserRepository; |
||
| 126 | /** |
||
| 127 | * @var AccountToUserGroupRepository |
||
| 128 | */ |
||
| 129 | private $accountToUserGroupRepository; |
||
| 130 | /** |
||
| 131 | * @var FileCacheInterface |
||
| 132 | */ |
||
| 133 | private $colorCache; |
||
| 134 | /** |
||
| 135 | * @var array |
||
| 136 | */ |
||
| 137 | private $accountColor; |
||
| 138 | /** |
||
| 139 | * @var AccountRepository |
||
| 140 | */ |
||
| 141 | private $accountRepository; |
||
| 142 | /** |
||
| 143 | * @var string |
||
| 144 | */ |
||
| 145 | private $cleanString; |
||
| 146 | /** |
||
| 147 | * @var string |
||
| 148 | */ |
||
| 149 | private $filterOperator; |
||
| 150 | |||
| 151 | /** |
||
| 152 | * Procesar los resultados de la búsqueda y crear la variable que contiene los datos de cada cuenta |
||
| 153 | * a mostrar. |
||
| 154 | * |
||
| 155 | * @param AccountSearchFilter $accountSearchFilter |
||
| 156 | * |
||
| 157 | * @return QueryResult |
||
| 158 | * @throws ConstraintException |
||
| 159 | * @throws QueryException |
||
| 160 | * @throws SPException |
||
| 161 | */ |
||
| 162 | public function processSearchResults(AccountSearchFilter $accountSearchFilter) |
||
| 163 | { |
||
| 164 | $accountSearchFilter->setStringFilters($this->analyzeQueryFilters($accountSearchFilter->getTxtSearch())); |
||
| 165 | |||
| 166 | if ($accountSearchFilter->getFilterOperator() === null |
||
| 167 | || $this->filterOperator !== null |
||
| 168 | ) { |
||
| 169 | $accountSearchFilter->setFilterOperator($this->filterOperator); |
||
| 170 | } |
||
| 171 | |||
| 172 | $accountSearchFilter->setCleanTxtSearch($this->cleanString); |
||
| 173 | |||
| 174 | $queryResult = $this->accountRepository->getByFilter($accountSearchFilter, $this->accountFilterUser->getFilter($accountSearchFilter->getGlobalSearch())); |
||
|
|
|||
| 175 | |||
| 176 | // Variables de configuración |
||
| 177 | $maxTextLength = $this->configData->isResultsAsCards() ? 40 : 60; |
||
| 178 | |||
| 179 | $accountLinkEnabled = $this->context->getUserData()->getPreferences()->isAccountLink() || $this->configData->isAccountLink(); |
||
| 180 | $favorites = $this->dic->get(AccountToFavoriteService::class)->getForUserId($this->context->getUserData()->getId()); |
||
| 181 | |||
| 182 | $accountsData = []; |
||
| 183 | |||
| 184 | /** @var AccountSearchVData $accountSearchData */ |
||
| 185 | foreach ($queryResult->getDataAsArray() as $accountSearchData) { |
||
| 186 | $cache = $this->getCacheForAccount($accountSearchData); |
||
| 187 | |||
| 188 | // Obtener la ACL de la cuenta |
||
| 189 | $accountAcl = $this->accountAclService->getAcl( |
||
| 190 | Acl::ACCOUNT_SEARCH, |
||
| 191 | AccountAclDto::makeFromAccountSearch($accountSearchData, $cache->getUsers(), $cache->getUserGroups()) |
||
| 192 | ); |
||
| 193 | |||
| 194 | // Propiedades de búsqueda de cada cuenta |
||
| 195 | $accountsSearchItem = new AccountSearchItem($accountSearchData, $accountAcl, $this->configData); |
||
| 196 | |||
| 197 | if (!$accountSearchData->getIsPrivate()) { |
||
| 198 | $accountsSearchItem->setUsers($cache->getUsers()); |
||
| 199 | $accountsSearchItem->setUserGroups($cache->getUserGroups()); |
||
| 200 | } |
||
| 201 | |||
| 202 | $accountsSearchItem->setTags($this->accountToTagRepository->getTagsByAccountId($accountSearchData->getId())->getDataAsArray()); |
||
| 203 | $accountsSearchItem->setTextMaxLength($maxTextLength); |
||
| 204 | $accountsSearchItem->setColor($this->pickAccountColor($accountSearchData->getClientId())); |
||
| 205 | $accountsSearchItem->setLink($accountLinkEnabled); |
||
| 206 | $accountsSearchItem->setFavorite(isset($favorites[$accountSearchData->getId()])); |
||
| 207 | |||
| 208 | $accountsData[] = $accountsSearchItem; |
||
| 209 | } |
||
| 210 | |||
| 211 | return QueryResult::fromResults($accountsData, $queryResult->getTotalNumRows()); |
||
| 212 | } |
||
| 213 | |||
| 214 | /** |
||
| 215 | * Analizar la cadena de consulta por eqituetas especiales y devolver un objeto |
||
| 216 | * QueryCondition con los filtros |
||
| 217 | * |
||
| 218 | * @param $string |
||
| 219 | * |
||
| 220 | * @return QueryCondition |
||
| 221 | * @throws ContainerExceptionInterface |
||
| 222 | * @throws NotFoundExceptionInterface |
||
| 223 | */ |
||
| 224 | public function analyzeQueryFilters($string) |
||
| 225 | { |
||
| 226 | $this->cleanString = null; |
||
| 227 | $this->filterOperator = null; |
||
| 228 | |||
| 229 | $queryCondition = new QueryCondition(); |
||
| 230 | |||
| 231 | $match = preg_match_all( |
||
| 232 | '/(?<search>(?<!:)\b[^:]+\b(?!:))|(?<filter_subject>[a-zа-я_]+):(?!\s]*)"?(?<filter_condition>[^":]+)"?/u', |
||
| 233 | $string, |
||
| 234 | $filters |
||
| 235 | ); |
||
| 236 | |||
| 237 | if ($match !== false && $match > 0) { |
||
| 238 | if (!empty($filters['search'][0])) { |
||
| 239 | $this->cleanString = Filter::safeSearchString(trim($filters['search'][0])); |
||
| 240 | } |
||
| 241 | |||
| 242 | $filtersAndValues = array_filter( |
||
| 243 | array_combine( |
||
| 244 | $filters['filter_subject'], |
||
| 245 | $filters['filter_condition'] |
||
| 246 | ) |
||
| 247 | ); |
||
| 248 | |||
| 249 | if (!empty($filtersAndValues)) { |
||
| 250 | $filtersItem = array_filter($filtersAndValues, function ($value, $key) { |
||
| 251 | return in_array($key, self::FILTERS['items']['subject'], true) |
||
| 252 | && $value !== ''; |
||
| 253 | }, ARRAY_FILTER_USE_BOTH); |
||
| 254 | |||
| 255 | if (!empty($filtersItem)) { |
||
| 256 | $this->processFilterItems($filtersItem, $queryCondition); |
||
| 257 | } |
||
| 258 | |||
| 259 | $filtersOperator = array_filter($filtersAndValues, function ($value, $key) { |
||
| 260 | return in_array($key, self::FILTERS['operator']['subject'], true) |
||
| 261 | && in_array($value, self::FILTERS['operator']['condition'], true); |
||
| 262 | }, ARRAY_FILTER_USE_BOTH); |
||
| 263 | |||
| 264 | if (!empty($filtersOperator)) { |
||
| 265 | $this->processFilterOperator($filtersOperator); |
||
| 266 | } |
||
| 267 | |||
| 268 | $filtersCondition = array_filter(array_map(function ($subject, $condition) { |
||
| 269 | if (in_array($subject, self::FILTERS['condition']['subject'], true) |
||
| 270 | && in_array($condition, self::FILTERS['condition']['condition'], true) |
||
| 271 | ) { |
||
| 272 | return $subject . ':' . $condition; |
||
| 273 | } |
||
| 274 | |||
| 275 | return null; |
||
| 276 | }, $filters['filter_subject'], $filters['filter_condition'])); |
||
| 277 | |||
| 278 | if (!empty($filtersCondition)) { |
||
| 279 | $this->processFilterIs($filtersCondition, $queryCondition); |
||
| 280 | } |
||
| 281 | } |
||
| 282 | } |
||
| 283 | |||
| 284 | return $queryCondition; |
||
| 285 | } |
||
| 286 | |||
| 287 | /** |
||
| 288 | * @param array $filters |
||
| 289 | * @param QueryCondition $queryCondition |
||
| 290 | */ |
||
| 291 | private function processFilterItems(array $filters, QueryCondition $queryCondition) |
||
| 292 | { |
||
| 293 | foreach ($filters as $filter => $text) { |
||
| 294 | try { |
||
| 295 | switch ($filter) { |
||
| 296 | case 'user': |
||
| 297 | $userData = $this->dic->get(UserService::class)->getByLogin(Filter::safeSearchString($text)); |
||
| 298 | |||
| 299 | if (is_object($userData)) { |
||
| 300 | $queryCondition->addFilter( |
||
| 301 | 'Account.userId = ? OR Account.userGroupId = ? OR Account.id IN |
||
| 302 | (SELECT AccountToUser.accountId FROM AccountToUser WHERE AccountToUser.accountId = Account.id AND AccountToUser.userId = ? |
||
| 303 | UNION |
||
| 304 | SELECT AccountToUserGroup.accountId FROM AccountToUserGroup WHERE AccountToUserGroup.accountId = Account.id AND AccountToUserGroup.userGroupId = ?)', |
||
| 305 | [$userData->getId(), $userData->getUserGroupId(), $userData->getId(), $userData->getUserGroupId()]); |
||
| 306 | } |
||
| 307 | break; |
||
| 308 | case 'owner': |
||
| 309 | $text = '%' . Filter::safeSearchString($text) . '%'; |
||
| 310 | $queryCondition->addFilter( |
||
| 311 | 'Account.userLogin LIKE ? OR Account.userName LIKE ?', |
||
| 312 | [$text, $text]); |
||
| 313 | break; |
||
| 314 | case 'group': |
||
| 315 | $userGroupData = $this->dic->get(UserGroupService::class)->getByName(Filter::safeSearchString($text)); |
||
| 316 | |||
| 317 | if (is_object($userGroupData)) { |
||
| 318 | $queryCondition->addFilter( |
||
| 319 | 'Account.userGroupId = ? OR Account.id IN (SELECT AccountToUserGroup.accountId FROM AccountToUserGroup WHERE AccountToUserGroup.accountId = id AND AccountToUserGroup.userGroupId = ?)', |
||
| 320 | [$userGroupData->getId(), $userGroupData->getId()]); |
||
| 321 | } |
||
| 322 | break; |
||
| 323 | case 'maingroup': |
||
| 324 | $queryCondition->addFilter('Account.userGroupName LIKE ?', ['%' . Filter::safeSearchString($text) . '%']); |
||
| 325 | break; |
||
| 326 | case 'file': |
||
| 327 | $queryCondition->addFilter('Account.id IN (SELECT AccountFile.accountId FROM AccountFile WHERE AccountFile.name LIKE ?)', ['%' . $text . '%']); |
||
| 328 | break; |
||
| 329 | case 'id': |
||
| 330 | $queryCondition->addFilter('Account.id = ?', [(int)$text]); |
||
| 331 | break; |
||
| 332 | case 'client': |
||
| 333 | $queryCondition->addFilter('Account.clientName LIKE ?', ['%' . Filter::safeSearchString($text) . '%']); |
||
| 334 | break; |
||
| 335 | case 'category': |
||
| 336 | $queryCondition->addFilter('Account.categoryName LIKE ?', ['%' . Filter::safeSearchString($text) . '%']); |
||
| 337 | break; |
||
| 338 | case 'name_regex': |
||
| 339 | $queryCondition->addFilter('Account.name REGEXP ?', [$text]); |
||
| 340 | break; |
||
| 341 | } |
||
| 342 | } catch (Exception $e) { |
||
| 343 | processException($e); |
||
| 344 | } |
||
| 345 | } |
||
| 346 | } |
||
| 347 | |||
| 348 | /** |
||
| 349 | * @param array $filters |
||
| 350 | */ |
||
| 351 | private function processFilterOperator(array $filters) |
||
| 352 | { |
||
| 353 | switch ($filters['op']) { |
||
| 354 | case 'and': |
||
| 355 | $this->filterOperator = QueryCondition::CONDITION_AND; |
||
| 356 | break; |
||
| 357 | case 'or': |
||
| 358 | $this->filterOperator = QueryCondition::CONDITION_OR; |
||
| 359 | break; |
||
| 360 | } |
||
| 361 | } |
||
| 362 | |||
| 363 | /** |
||
| 364 | * @param array $filters |
||
| 365 | * @param QueryCondition $queryCondition |
||
| 366 | */ |
||
| 367 | private function processFilterIs(array $filters, QueryCondition $queryCondition) |
||
| 368 | { |
||
| 369 | foreach ($filters as $filter) { |
||
| 370 | switch ($filter) { |
||
| 371 | case 'is:expired': |
||
| 372 | $queryCondition->addFilter( |
||
| 373 | 'Account.passDateChange > 0 AND UNIX_TIMESTAMP() > Account.passDateChange', |
||
| 374 | []); |
||
| 375 | break; |
||
| 376 | case 'not:expired': |
||
| 377 | $queryCondition->addFilter( |
||
| 378 | 'Account.passDateChange = 0 OR Account.passDateChange IS NULL OR UNIX_TIMESTAMP() < Account.passDateChange', |
||
| 379 | []); |
||
| 380 | break; |
||
| 381 | case 'is:private': |
||
| 382 | $queryCondition->addFilter( |
||
| 383 | '(Account.isPrivate = 1 AND Account.userId = ?) OR (Account.isPrivateGroup = 1 AND Account.userGroupId = ?)', |
||
| 384 | [$this->context->getUserData()->getId(), $this->context->getUserData()->getUserGroupId()]); |
||
| 385 | break; |
||
| 386 | case 'not:private': |
||
| 387 | $queryCondition->addFilter( |
||
| 388 | '(Account.isPrivate = 0 OR Account.isPrivate IS NULL) AND (Account.isPrivateGroup = 0 OR Account.isPrivateGroup IS NULL)'); |
||
| 389 | break; |
||
| 390 | } |
||
| 391 | } |
||
| 392 | } |
||
| 393 | |||
| 394 | /** |
||
| 395 | * Devolver los accesos desde la caché |
||
| 396 | * |
||
| 397 | * @param AccountSearchVData $accountSearchData |
||
| 398 | * |
||
| 399 | * @return AccountCache |
||
| 400 | * @throws ConstraintException |
||
| 401 | * @throws QueryException |
||
| 402 | */ |
||
| 403 | protected function getCacheForAccount(AccountSearchVData $accountSearchData) |
||
| 404 | { |
||
| 405 | $accountId = $accountSearchData->getId(); |
||
| 406 | |||
| 407 | /** @var AccountCache[] $cache */ |
||
| 408 | $cache = $this->context->getAccountsCache(); |
||
| 409 | |||
| 410 | $hasCache = $cache !== null; |
||
| 411 | |||
| 412 | if ($cache === false |
||
| 413 | || !isset($cache[$accountId]) |
||
| 414 | || $cache[$accountId]->getTime() < (int)strtotime($accountSearchData->getDateEdit()) |
||
| 415 | ) { |
||
| 416 | $cache[$accountId] = new AccountCache( |
||
| 417 | $accountId, |
||
| 418 | $this->accountToUserRepository->getUsersByAccountId($accountId)->getDataAsArray(), |
||
| 419 | $this->accountToUserGroupRepository->getUserGroupsByAccountId($accountId)->getDataAsArray()); |
||
| 420 | |||
| 421 | if ($hasCache) { |
||
| 422 | $this->context->setAccountsCache($cache); |
||
| 423 | } |
||
| 424 | } |
||
| 425 | |||
| 426 | return $cache[$accountId]; |
||
| 427 | } |
||
| 428 | |||
| 429 | /** |
||
| 430 | * Seleccionar un color para la cuenta |
||
| 431 | * |
||
| 432 | * @param int $id El id del elemento a asignar |
||
| 433 | * |
||
| 434 | * @return string |
||
| 435 | */ |
||
| 436 | private function pickAccountColor($id) |
||
| 437 | { |
||
| 438 | if ($this->accountColor !== null && isset($this->accountColor[$id])) { |
||
| 439 | return $this->accountColor[$id]; |
||
| 440 | } |
||
| 441 | |||
| 442 | // Se asigna el color de forma aleatoria a cada id |
||
| 443 | $this->accountColor[$id] = '#' . self::COLORS[array_rand(self::COLORS)]; |
||
| 444 | |||
| 445 | try { |
||
| 446 | $this->colorCache->save($this->accountColor); |
||
| 447 | |||
| 448 | logger('Saved accounts color cache'); |
||
| 449 | |||
| 450 | return $this->accountColor[$id]; |
||
| 451 | } catch (FileException $e) { |
||
| 452 | processException($e); |
||
| 453 | |||
| 454 | return ''; |
||
| 455 | } |
||
| 456 | } |
||
| 457 | |||
| 458 | /** |
||
| 459 | * @return string |
||
| 460 | */ |
||
| 461 | public function getCleanString() |
||
| 464 | } |
||
| 465 | |||
| 466 | /** |
||
| 467 | * @throws ContainerExceptionInterface |
||
| 468 | * @throws NotFoundExceptionInterface |
||
| 469 | */ |
||
| 470 | protected function initialize() |
||
| 482 | } |
||
| 483 | |||
| 484 | /** |
||
| 485 | * Load colors from cache |
||
| 486 | */ |
||
| 487 | private function loadColors() |
||
| 495 | } |
||
| 496 | } |
||
| 497 | } |