Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
   1  <?php
   2  
   3  declare(strict_types=1);
   4  
   5  namespace OpenSpout\Writer\Common\Manager;
   6  
   7  use OpenSpout\Common\Helper\StringHelper;
   8  use OpenSpout\Writer\Common\Entity\Sheet;
   9  use OpenSpout\Writer\Exception\InvalidSheetNameException;
  10  
  11  /**
  12   * @internal
  13   */
  14  final class SheetManager
  15  {
  16      /**
  17       * Sheet name should not exceed 31 characters.
  18       */
  19      public const MAX_LENGTH_SHEET_NAME = 31;
  20  
  21      /**
  22       * Invalid characters that cannot be contained in the sheet name.
  23       */
  24      private const INVALID_CHARACTERS_IN_SHEET_NAME = ['\\', '/', '?', '*', ':', '[', ']'];
  25  
  26      /** @var array<string, array<int, string>> Associative array [WORKBOOK_ID] => [[SHEET_INDEX] => [SHEET_NAME]] keeping track of sheets' name to enforce uniqueness per workbook */
  27      private static array $SHEETS_NAME_USED = [];
  28  
  29      private StringHelper $stringHelper;
  30  
  31      /**
  32       * SheetManager constructor.
  33       */
  34      public function __construct(StringHelper $stringHelper)
  35      {
  36          $this->stringHelper = $stringHelper;
  37      }
  38  
  39      /**
  40       * Throws an exception if the given sheet's name is not valid.
  41       *
  42       * @see Sheet::setName for validity rules.
  43       *
  44       * @param Sheet $sheet The sheet whose future name is checked
  45       *
  46       * @throws \OpenSpout\Writer\Exception\InvalidSheetNameException if the sheet's name is invalid
  47       */
  48      public function throwIfNameIsInvalid(string $name, Sheet $sheet): void
  49      {
  50          $failedRequirements = [];
  51          $nameLength = $this->stringHelper->getStringLength($name);
  52  
  53          if (!$this->isNameUnique($name, $sheet)) {
  54              $failedRequirements[] = 'It should be unique';
  55          } elseif (0 === $nameLength) {
  56              $failedRequirements[] = 'It should not be blank';
  57          } else {
  58              if ($nameLength > self::MAX_LENGTH_SHEET_NAME) {
  59                  $failedRequirements[] = 'It should not exceed 31 characters';
  60              }
  61  
  62              if ($this->doesContainInvalidCharacters($name)) {
  63                  $failedRequirements[] = 'It should not contain these characters: \\ / ? * : [ or ]';
  64              }
  65  
  66              if ($this->doesStartOrEndWithSingleQuote($name)) {
  67                  $failedRequirements[] = 'It should not start or end with a single quote';
  68              }
  69          }
  70  
  71          if (0 !== \count($failedRequirements)) {
  72              $errorMessage = "The sheet's name (\"{$name}\") is invalid. It did not respect these rules:\n - ";
  73              $errorMessage .= implode("\n - ", $failedRequirements);
  74  
  75              throw new InvalidSheetNameException($errorMessage);
  76          }
  77      }
  78  
  79      /**
  80       * @param string $workbookId Workbook ID associated to a Sheet
  81       */
  82      public function markWorkbookIdAsUsed(string $workbookId): void
  83      {
  84          if (!isset(self::$SHEETS_NAME_USED[$workbookId])) {
  85              self::$SHEETS_NAME_USED[$workbookId] = [];
  86          }
  87      }
  88  
  89      public function markSheetNameAsUsed(Sheet $sheet): void
  90      {
  91          self::$SHEETS_NAME_USED[$sheet->getAssociatedWorkbookId()][$sheet->getIndex()] = $sheet->getName();
  92      }
  93  
  94      /**
  95       * Returns whether the given name contains at least one invalid character.
  96       *
  97       * @return bool TRUE if the name contains invalid characters, FALSE otherwise
  98       */
  99      private function doesContainInvalidCharacters(string $name): bool
 100      {
 101          return str_replace(self::INVALID_CHARACTERS_IN_SHEET_NAME, '', $name) !== $name;
 102      }
 103  
 104      /**
 105       * Returns whether the given name starts or ends with a single quote.
 106       *
 107       * @return bool TRUE if the name starts or ends with a single quote, FALSE otherwise
 108       */
 109      private function doesStartOrEndWithSingleQuote(string $name): bool
 110      {
 111          $startsWithSingleQuote = (0 === $this->stringHelper->getCharFirstOccurrencePosition('\'', $name));
 112          $endsWithSingleQuote = ($this->stringHelper->getCharLastOccurrencePosition('\'', $name) === ($this->stringHelper->getStringLength($name) - 1));
 113  
 114          return $startsWithSingleQuote || $endsWithSingleQuote;
 115      }
 116  
 117      /**
 118       * Returns whether the given name is unique.
 119       *
 120       * @param Sheet $sheet The sheet whose future name is checked
 121       *
 122       * @return bool TRUE if the name is unique, FALSE otherwise
 123       */
 124      private function isNameUnique(string $name, Sheet $sheet): bool
 125      {
 126          foreach (self::$SHEETS_NAME_USED[$sheet->getAssociatedWorkbookId()] as $sheetIndex => $sheetName) {
 127              if ($sheetIndex !== $sheet->getIndex() && $sheetName === $name) {
 128                  return false;
 129              }
 130          }
 131  
 132          return true;
 133      }
 134  }