Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Calculation;
   4  
   5  use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
   6  
   7  class BinaryComparison
   8  {
   9      /**
  10       * Epsilon Precision used for comparisons in calculations.
  11       */
  12      private const DELTA = 0.1e-12;
  13  
  14      /**
  15       * Compare two strings in the same way as strcmp() except that lowercase come before uppercase letters.
  16       *
  17       * @param null|string $str1 First string value for the comparison
  18       * @param null|string $str2 Second string value for the comparison
  19       */
  20      private static function strcmpLowercaseFirst($str1, $str2): int
  21      {
  22          $inversedStr1 = StringHelper::strCaseReverse($str1 ?? '');
  23          $inversedStr2 = StringHelper::strCaseReverse($str2 ?? '');
  24  
  25          return strcmp($inversedStr1, $inversedStr2);
  26      }
  27  
  28      /**
  29       * PHP8.1 deprecates passing null to strcmp.
  30       *
  31       * @param null|string $str1 First string value for the comparison
  32       * @param null|string $str2 Second string value for the comparison
  33       */
  34      private static function strcmpAllowNull($str1, $str2): int
  35      {
  36          return strcmp($str1 ?? '', $str2 ?? '');
  37      }
  38  
  39      /**
  40       * @param mixed $operand1
  41       * @param mixed $operand2
  42       */
  43      public static function compare($operand1, $operand2, string $operator): bool
  44      {
  45          //    Simple validate the two operands if they are string values
  46          if (is_string($operand1) && $operand1 > '' && $operand1[0] == Calculation::FORMULA_STRING_QUOTE) {
  47              $operand1 = Calculation::unwrapResult($operand1);
  48          }
  49          if (is_string($operand2) && $operand2 > '' && $operand2[0] == Calculation::FORMULA_STRING_QUOTE) {
  50              $operand2 = Calculation::unwrapResult($operand2);
  51          }
  52  
  53          // Use case insensitive comparaison if not OpenOffice mode
  54          if (Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) {
  55              if (is_string($operand1)) {
  56                  $operand1 = StringHelper::strToUpper($operand1);
  57              }
  58              if (is_string($operand2)) {
  59                  $operand2 = StringHelper::strToUpper($operand2);
  60              }
  61          }
  62  
  63          $useLowercaseFirstComparison = is_string($operand1) &&
  64              is_string($operand2) &&
  65              Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE;
  66  
  67          return self::evaluateComparison($operand1, $operand2, $operator, $useLowercaseFirstComparison);
  68      }
  69  
  70      /**
  71       * @param mixed $operand1
  72       * @param mixed $operand2
  73       */
  74      private static function evaluateComparison($operand1, $operand2, string $operator, bool $useLowercaseFirstComparison): bool
  75      {
  76          switch ($operator) {
  77              //    Equality
  78              case '=':
  79                  return self::equal($operand1, $operand2);
  80              //    Greater than
  81              case '>':
  82                  return self::greaterThan($operand1, $operand2, $useLowercaseFirstComparison);
  83              //    Less than
  84              case '<':
  85                  return self::lessThan($operand1, $operand2, $useLowercaseFirstComparison);
  86              //    Greater than or equal
  87              case '>=':
  88                  return self::greaterThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison);
  89              //    Less than or equal
  90              case '<=':
  91                  return self::lessThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison);
  92              //    Inequality
  93              case '<>':
  94                  return self::notEqual($operand1, $operand2);
  95              default:
  96                  throw new Exception('Unsupported binary comparison operator');
  97          }
  98      }
  99  
 100      /**
 101       * @param mixed $operand1
 102       * @param mixed $operand2
 103       */
 104      private static function equal($operand1, $operand2): bool
 105      {
 106          if (is_numeric($operand1) && is_numeric($operand2)) {
 107              $result = (abs($operand1 - $operand2) < self::DELTA);
 108          } elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) {
 109              $result = $operand1 == $operand2;
 110          } else {
 111              $result = self::strcmpAllowNull($operand1, $operand2) == 0;
 112          }
 113  
 114          return $result;
 115      }
 116  
 117      /**
 118       * @param mixed $operand1
 119       * @param mixed $operand2
 120       */
 121      private static function greaterThanOrEqual($operand1, $operand2, bool $useLowercaseFirstComparison): bool
 122      {
 123          if (is_numeric($operand1) && is_numeric($operand2)) {
 124              $result = ((abs($operand1 - $operand2) < self::DELTA) || ($operand1 > $operand2));
 125          } elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) {
 126              $result = $operand1 >= $operand2;
 127          } elseif ($useLowercaseFirstComparison) {
 128              $result = self::strcmpLowercaseFirst($operand1, $operand2) >= 0;
 129          } else {
 130              $result = self::strcmpAllowNull($operand1, $operand2) >= 0;
 131          }
 132  
 133          return $result;
 134      }
 135  
 136      /**
 137       * @param mixed $operand1
 138       * @param mixed $operand2
 139       */
 140      private static function lessThanOrEqual($operand1, $operand2, bool $useLowercaseFirstComparison): bool
 141      {
 142          if (is_numeric($operand1) && is_numeric($operand2)) {
 143              $result = ((abs($operand1 - $operand2) < self::DELTA) || ($operand1 < $operand2));
 144          } elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) {
 145              $result = $operand1 <= $operand2;
 146          } elseif ($useLowercaseFirstComparison) {
 147              $result = self::strcmpLowercaseFirst($operand1, $operand2) <= 0;
 148          } else {
 149              $result = self::strcmpAllowNull($operand1, $operand2) <= 0;
 150          }
 151  
 152          return $result;
 153      }
 154  
 155      /**
 156       * @param mixed $operand1
 157       * @param mixed $operand2
 158       */
 159      private static function greaterThan($operand1, $operand2, bool $useLowercaseFirstComparison): bool
 160      {
 161          return self::lessThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison) !== true;
 162      }
 163  
 164      /**
 165       * @param mixed $operand1
 166       * @param mixed $operand2
 167       */
 168      private static function lessThan($operand1, $operand2, bool $useLowercaseFirstComparison): bool
 169      {
 170          return self::greaterThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison) !== true;
 171      }
 172  
 173      /**
 174       * @param mixed $operand1
 175       * @param mixed $operand2
 176       */
 177      private static function notEqual($operand1, $operand2): bool
 178      {
 179          return self::equal($operand1, $operand2) !== true;
 180      }
 181  }