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