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\LookupRef;
   4  
   5  use PhpOffice\PhpSpreadsheet\Calculation\Functions;
   6  use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
   7  use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
   8  
   9  class Unique
  10  {
  11      /**
  12       * UNIQUE
  13       * The UNIQUE function searches for value either from a one-row or one-column range or from an array.
  14       *
  15       * @param mixed $lookupVector The range of cells being searched
  16       * @param mixed $byColumn Whether the uniqueness should be determined by row (the default) or by column
  17       * @param mixed $exactlyOnce Whether the function should return only entries that occur just once in the list
  18       *
  19       * @return mixed The unique values from the search range
  20       */
  21      public static function unique($lookupVector, $byColumn = false, $exactlyOnce = false)
  22      {
  23          if (!is_array($lookupVector)) {
  24              // Scalars are always returned "as is"
  25              return $lookupVector;
  26          }
  27  
  28          $byColumn = (bool) $byColumn;
  29          $exactlyOnce = (bool) $exactlyOnce;
  30  
  31          return ($byColumn === true)
  32              ? self::uniqueByColumn($lookupVector, $exactlyOnce)
  33              : self::uniqueByRow($lookupVector, $exactlyOnce);
  34      }
  35  
  36      /**
  37       * @return mixed
  38       */
  39      private static function uniqueByRow(array $lookupVector, bool $exactlyOnce)
  40      {
  41          // When not $byColumn, we count whole rows or values, not individual values
  42          //      so implode each row into a single string value
  43          array_walk(
  44              $lookupVector,
  45              function (array &$value): void {
  46                  $value = implode(chr(0x00), $value);
  47              }
  48          );
  49  
  50          $result = self::countValuesCaseInsensitive($lookupVector);
  51  
  52          if ($exactlyOnce === true) {
  53              $result = self::exactlyOnceFilter($result);
  54          }
  55  
  56          if (count($result) === 0) {
  57              return ExcelError::CALC();
  58          }
  59  
  60          $result = array_keys($result);
  61  
  62          // restore rows from their strings
  63          array_walk(
  64              $result,
  65              function (string &$value): void {
  66                  $value = explode(chr(0x00), $value);
  67              }
  68          );
  69  
  70          return (count($result) === 1) ? array_pop($result) : $result;
  71      }
  72  
  73      /**
  74       * @return mixed
  75       */
  76      private static function uniqueByColumn(array $lookupVector, bool $exactlyOnce)
  77      {
  78          $flattenedLookupVector = Functions::flattenArray($lookupVector);
  79  
  80          if (count($lookupVector, COUNT_RECURSIVE) > count($flattenedLookupVector, COUNT_RECURSIVE) + 1) {
  81              // We're looking at a full column check (multiple rows)
  82              $transpose = Matrix::transpose($lookupVector);
  83              $result = self::uniqueByRow($transpose, $exactlyOnce);
  84  
  85              return (is_array($result)) ? Matrix::transpose($result) : $result;
  86          }
  87  
  88          $result = self::countValuesCaseInsensitive($flattenedLookupVector);
  89  
  90          if ($exactlyOnce === true) {
  91              $result = self::exactlyOnceFilter($result);
  92          }
  93  
  94          if (count($result) === 0) {
  95              return ExcelError::CALC();
  96          }
  97  
  98          $result = array_keys($result);
  99  
 100          return $result;
 101      }
 102  
 103      private static function countValuesCaseInsensitive(array $caseSensitiveLookupValues): array
 104      {
 105          $caseInsensitiveCounts = array_count_values(
 106              array_map(
 107                  function (string $value) {
 108                      return StringHelper::strToUpper($value);
 109                  },
 110                  $caseSensitiveLookupValues
 111              )
 112          );
 113  
 114          $caseSensitiveCounts = [];
 115          foreach ($caseInsensitiveCounts as $caseInsensitiveKey => $count) {
 116              if (is_numeric($caseInsensitiveKey)) {
 117                  $caseSensitiveCounts[$caseInsensitiveKey] = $count;
 118              } else {
 119                  foreach ($caseSensitiveLookupValues as $caseSensitiveValue) {
 120                      if ($caseInsensitiveKey === StringHelper::strToUpper($caseSensitiveValue)) {
 121                          $caseSensitiveCounts[$caseSensitiveValue] = $count;
 122  
 123                          break;
 124                      }
 125                  }
 126              }
 127          }
 128  
 129          return $caseSensitiveCounts;
 130      }
 131  
 132      private static function exactlyOnceFilter(array $values): array
 133      {
 134          return array_filter(
 135              $values,
 136              function ($value) {
 137                  return $value === 1;
 138              }
 139          );
 140      }
 141  }