| Conditions | 47 |
| Paths | 1866 |
| Total Lines | 181 |
| Code Lines | 89 |
| Lines | 0 |
| Ratio | 0 % |
| Changes | 12 | ||
| Bugs | 1 | Features | 0 |
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:
If many parameters/temporary variables are present:
| 1 | <?php |
||
| 407 | public function render(?\XoopsDatabase $db = null) |
||
| 408 | { |
||
| 409 | // 1) Explicit injection |
||
| 410 | // 2) Legacy global |
||
| 411 | // 3) Factory (if available) |
||
| 412 | if ($db === null && isset($GLOBALS['xoopsDB']) && $GLOBALS['xoopsDB'] instanceof \XoopsDatabase) { |
||
| 413 | $db = $GLOBALS['xoopsDB']; |
||
| 414 | } |
||
| 415 | |||
| 416 | if ($db === null && class_exists('\XoopsDatabaseFactory')) { |
||
| 417 | try { |
||
| 418 | $db = \XoopsDatabaseFactory::getDatabaseConnection(); |
||
| 419 | } catch (\Throwable $e) { |
||
| 420 | throw new \RuntimeException('Database connection required to render Criteria: ' . $e->getMessage(), 0, $e); |
||
| 421 | } |
||
| 422 | } |
||
| 423 | |||
| 424 | if (!$db) { |
||
| 425 | throw new \RuntimeException('Database connection required to render Criteria'); |
||
| 426 | } |
||
| 427 | |||
| 428 | $col = (string)($this->column ?? ''); |
||
| 429 | |||
| 430 | if ($col === '') { |
||
| 431 | return ''; |
||
| 432 | } |
||
| 433 | |||
| 434 | $backtick = (strpos($col, '.') === false && strpos($col, '(') === false) ? '`' : ''; |
||
| 435 | $clause = (empty($this->prefix) ? '' : "{$this->prefix}.") . $backtick . $col . $backtick; |
||
| 436 | |||
| 437 | if (!empty($this->function)) { |
||
| 438 | $clause = sprintf($this->function, $clause); |
||
| 439 | } |
||
| 440 | |||
| 441 | $op = strtoupper((string)$this->operator); |
||
| 442 | |||
| 443 | // NULL operators |
||
| 444 | if ($op === 'IS NULL' || $op === 'IS NOT NULL') { |
||
| 445 | return $clause . ' ' . $op; |
||
| 446 | } |
||
| 447 | |||
| 448 | /** |
||
| 449 | * IN / NOT IN |
||
| 450 | */ |
||
| 451 | if ($op === 'IN' || $op === 'NOT IN') { |
||
| 452 | // Modern safe path: array input |
||
| 453 | if (is_array($this->value)) { |
||
| 454 | $parts = []; |
||
| 455 | foreach ($this->value as $v) { |
||
| 456 | if (is_int($v) || (is_string($v) && preg_match('/^-?\d+$/', $v))) { |
||
| 457 | $parts[] = (string)(int)$v; |
||
| 458 | } else { |
||
| 459 | $parts[] = $db->quote((string)$v); |
||
| 460 | } |
||
| 461 | } |
||
| 462 | return $clause . ' ' . $op . ' (' . implode(',', $parts) . ')'; |
||
| 463 | } |
||
| 464 | |||
| 465 | // Legacy format: preformatted string in parentheses |
||
| 466 | $legacy = (string)$this->value; |
||
| 467 | |||
| 468 | // FIRST: strict validation of legacy syntax |
||
| 469 | if (!self::isSafeLegacyInList($legacy)) { |
||
| 470 | // Malformed → treat as a single literal safely |
||
| 471 | return $clause . ' ' . $op . ' (' . $db->quote($legacy) . ')'; |
||
| 472 | } |
||
| 473 | |||
| 474 | // If legacy logging is not enabled, just pass through |
||
| 475 | if (!self::isLegacyLogEnabled()) { |
||
| 476 | return $clause . ' ' . $op . ' ' . $legacy; |
||
| 477 | } |
||
| 478 | |||
| 479 | // Build log message |
||
| 480 | $message = sprintf( |
||
| 481 | 'Legacy Criteria IN format used for column "%s" with value "%s"', |
||
| 482 | $this->column, |
||
| 483 | $legacy |
||
| 484 | ); |
||
| 485 | |||
| 486 | // Only pay backtrace cost in debug mode |
||
| 487 | if (defined('XOOPS_DEBUG') && XOOPS_DEBUG) { |
||
| 488 | $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); |
||
| 489 | $caller = $bt[1] ?? []; |
||
| 490 | $file = $caller['file'] ?? 'unknown'; |
||
| 491 | $line = $caller['line'] ?? 0; |
||
| 492 | $message .= sprintf(' at %s:%d', $file, $line); |
||
| 493 | } |
||
| 494 | |||
| 495 | if (class_exists('XoopsLogger')) { |
||
| 496 | \XoopsLogger::getInstance() |
||
| 497 | ->addExtra('CriteriaLegacyIN', $message); |
||
| 498 | } else { |
||
| 499 | error_log($message); |
||
| 500 | } |
||
| 501 | |||
| 502 | if (defined('XOOPS_DEBUG') && XOOPS_DEBUG) { |
||
| 503 | trigger_error($message, E_USER_DEPRECATED); |
||
| 504 | } |
||
| 505 | |||
| 506 | return $clause . ' ' . $op . ' ' . $legacy; |
||
| 507 | } |
||
| 508 | |||
| 509 | // NOW it's safe to cast to string for other operators |
||
| 510 | $valStr = (string)$this->value; |
||
| 511 | |||
| 512 | // Empty value check |
||
| 513 | if (trim($valStr) === '' && !$this->allowEmptyValue) { |
||
| 514 | return ''; |
||
| 515 | } |
||
| 516 | |||
| 517 | /** |
||
| 518 | * LIKE / NOT LIKE |
||
| 519 | * - Preserves leading/trailing % as wildcards |
||
| 520 | * - Escapes inner backslashes |
||
| 521 | * - Optionally escapes inner % and _ when allowInnerWildcards is false |
||
| 522 | */ |
||
| 523 | if ($op === 'LIKE' || $op === 'NOT LIKE') { |
||
| 524 | $pattern = (string)$this->value; |
||
| 525 | |||
| 526 | // If pattern is only % signs, it's effectively "match everything" → no predicate |
||
| 527 | if ($op === 'LIKE' && $pattern !== '' && strspn($pattern, '%') === strlen($pattern)) { |
||
| 528 | return ''; |
||
| 529 | } |
||
| 530 | |||
| 531 | $len = strlen($pattern); |
||
| 532 | $lead = strspn($pattern, '%'); |
||
| 533 | $trail = strspn(strrev($pattern), '%'); |
||
| 534 | $coreLen = $len - $lead - $trail; |
||
| 535 | |||
| 536 | if ($coreLen <= 0) { |
||
| 537 | $final = $pattern; |
||
| 538 | } else { |
||
| 539 | $left = $lead > 0 ? substr($pattern, 0, $lead) : ''; |
||
| 540 | $core = substr($pattern, $lead, $coreLen); |
||
| 541 | $right = $trail > 0 ? substr($pattern, -$trail) : ''; |
||
| 542 | |||
| 543 | // Always escape backslashes in the core |
||
| 544 | $core = str_replace('\\', '\\\\', $core); |
||
| 545 | |||
| 546 | // If inner wildcards are NOT allowed, escape % and _ inside core |
||
| 547 | if (!$this->allowInnerWildcards) { |
||
| 548 | $core = strtr($core, [ |
||
| 549 | '%' => '\\%', |
||
| 550 | '_' => '\\_', |
||
| 551 | ]); |
||
| 552 | } |
||
| 553 | |||
| 554 | $final = $left . $core . $right; |
||
| 555 | } |
||
| 556 | |||
| 557 | $quoted = $db->quote($final); |
||
| 558 | return $clause . ' ' . $op . ' ' . $quoted; |
||
| 559 | } |
||
| 560 | |||
| 561 | /** |
||
| 562 | * All other operators: =, <, >, <=, >=, !=, <> |
||
| 563 | */ |
||
| 564 | |||
| 565 | // Backtick bypass for column-to-column comparisons |
||
| 566 | $len = strlen($valStr); |
||
| 567 | if ($len > 2 && $valStr[0] === '`' && $valStr[$len - 1] === '`') { |
||
| 568 | $inner = substr($valStr, 1, -1); |
||
| 569 | |||
| 570 | // Allow alphanumeric, underscore, dot, and dollar sign |
||
| 571 | // (valid in MySQL identifiers when backticked, incl. db.table) |
||
| 572 | if (preg_match('/^[a-zA-Z0-9_.$\\-]+$/', $inner)) { |
||
| 573 | $safeValue = $valStr; |
||
| 574 | } else { |
||
| 575 | // Old behavior: empty backticks on invalid identifier content |
||
| 576 | $safeValue = '``'; |
||
| 577 | } |
||
| 578 | } else { |
||
| 579 | // Regular value - keep integers numeric; quote strings |
||
| 580 | if (is_int($this->value) || (is_string($this->value) && preg_match('/^-?\d+$/', $this->value))) { |
||
| 581 | $safeValue = (string)(int)$this->value; |
||
| 582 | } else { |
||
| 583 | $safeValue = $db->quote((string)$this->value); |
||
| 584 | } |
||
| 585 | } |
||
| 586 | |||
| 587 | return $clause . ' ' . $op . ' ' . $safeValue; |
||
| 588 | } |
||
| 636 |