Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.
   1  <?php
   2  
   3  declare(strict_types=1);
   4  
   5  namespace OpenSpout\Writer\Common\Manager;
   6  
   7  use OpenSpout\Common\Entity\Row;
   8  use OpenSpout\Common\Exception\IOException;
   9  use OpenSpout\Common\Helper\StringHelper;
  10  use OpenSpout\Writer\Common\AbstractOptions;
  11  use OpenSpout\Writer\Common\Entity\Sheet;
  12  use OpenSpout\Writer\Common\Entity\Workbook;
  13  use OpenSpout\Writer\Common\Entity\Worksheet;
  14  use OpenSpout\Writer\Common\Helper\FileSystemWithRootFolderHelperInterface;
  15  use OpenSpout\Writer\Common\Manager\Style\StyleManagerInterface;
  16  use OpenSpout\Writer\Common\Manager\Style\StyleMerger;
  17  use OpenSpout\Writer\Exception\SheetNotFoundException;
  18  
  19  /**
  20   * @internal
  21   */
  22  abstract class AbstractWorkbookManager implements WorkbookManagerInterface
  23  {
  24      protected WorksheetManagerInterface $worksheetManager;
  25  
  26      /** @var StyleManagerInterface Manages styles */
  27      protected StyleManagerInterface $styleManager;
  28  
  29      /** @var FileSystemWithRootFolderHelperInterface Helper to perform file system operations */
  30      protected FileSystemWithRootFolderHelperInterface $fileSystemHelper;
  31  
  32      protected AbstractOptions $options;
  33  
  34      /** @var Workbook The workbook to manage */
  35      private Workbook $workbook;
  36  
  37      /** @var StyleMerger Helper to merge styles */
  38      private StyleMerger $styleMerger;
  39  
  40      /** @var Worksheet The worksheet where data will be written to */
  41      private Worksheet $currentWorksheet;
  42  
  43      public function __construct(
  44          Workbook $workbook,
  45          AbstractOptions $options,
  46          WorksheetManagerInterface $worksheetManager,
  47          StyleManagerInterface $styleManager,
  48          StyleMerger $styleMerger,
  49          FileSystemWithRootFolderHelperInterface $fileSystemHelper
  50      ) {
  51          $this->workbook = $workbook;
  52          $this->options = $options;
  53          $this->worksheetManager = $worksheetManager;
  54          $this->styleManager = $styleManager;
  55          $this->styleMerger = $styleMerger;
  56          $this->fileSystemHelper = $fileSystemHelper;
  57      }
  58  
  59      /**
  60       * Creates a new sheet in the workbook and make it the current sheet.
  61       * The writing will resume where it stopped (i.e. data won't be truncated).
  62       *
  63       * @return Worksheet The created sheet
  64       */
  65      final public function addNewSheetAndMakeItCurrent(): Worksheet
  66      {
  67          $worksheet = $this->addNewSheet();
  68          $this->setCurrentWorksheet($worksheet);
  69  
  70          return $worksheet;
  71      }
  72  
  73      /**
  74       * @return Worksheet[] All the workbook's sheets
  75       */
  76      final public function getWorksheets(): array
  77      {
  78          return $this->workbook->getWorksheets();
  79      }
  80  
  81      /**
  82       * Returns the current sheet.
  83       *
  84       * @return Worksheet The current sheet
  85       */
  86      final public function getCurrentWorksheet(): Worksheet
  87      {
  88          return $this->currentWorksheet;
  89      }
  90  
  91      /**
  92       * Sets the given sheet as the current one. New data will be written to this sheet.
  93       * The writing will resume where it stopped (i.e. data won't be truncated).
  94       *
  95       * @param Sheet $sheet The "external" sheet to set as current
  96       *
  97       * @throws SheetNotFoundException If the given sheet does not exist in the workbook
  98       */
  99      final public function setCurrentSheet(Sheet $sheet): void
 100      {
 101          $worksheet = $this->getWorksheetFromExternalSheet($sheet);
 102          if (null !== $worksheet) {
 103              $this->currentWorksheet = $worksheet;
 104          } else {
 105              throw new SheetNotFoundException('The given sheet does not exist in the workbook.');
 106          }
 107      }
 108  
 109      /**
 110       * Adds a row to the current sheet.
 111       * If shouldCreateNewSheetsAutomatically option is set to true, it will handle pagination
 112       * with the creation of new worksheets if one worksheet has reached its maximum capicity.
 113       *
 114       * @param Row $row The row to be added
 115       *
 116       * @throws IOException                                          If trying to create a new sheet and unable to open the sheet for writing
 117       * @throws \OpenSpout\Common\Exception\InvalidArgumentException
 118       */
 119      final public function addRowToCurrentWorksheet(Row $row): void
 120      {
 121          $currentWorksheet = $this->getCurrentWorksheet();
 122          if ($this->hasCurrentWorksheetReachedMaxRows()) {
 123              if (!$this->options->SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY) {
 124                  return;
 125              }
 126  
 127              $currentWorksheet = $this->addNewSheetAndMakeItCurrent();
 128          }
 129  
 130          $this->addRowToWorksheet($currentWorksheet, $row);
 131          $currentWorksheet->getExternalSheet()->incrementWrittenRowCount();
 132      }
 133  
 134      /**
 135       * Closes the workbook and all its associated sheets.
 136       * All the necessary files are written to disk and zipped together to create the final file.
 137       * All the temporary files are then deleted.
 138       *
 139       * @param resource $finalFilePointer Pointer to the spreadsheet that will be created
 140       */
 141      final public function close($finalFilePointer): void
 142      {
 143          $this->closeAllWorksheets();
 144          $this->closeRemainingObjects();
 145          $this->writeAllFilesToDiskAndZipThem($finalFilePointer);
 146          $this->cleanupTempFolder();
 147      }
 148  
 149      /**
 150       * @return int Maximum number of rows/columns a sheet can contain
 151       */
 152      abstract protected function getMaxRowsPerWorksheet(): int;
 153  
 154      /**
 155       * Closes custom objects that are still opened.
 156       */
 157      protected function closeRemainingObjects(): void
 158      {
 159          // do nothing by default
 160      }
 161  
 162      /**
 163       * Writes all the necessary files to disk and zip them together to create the final file.
 164       *
 165       * @param resource $finalFilePointer Pointer to the spreadsheet that will be created
 166       */
 167      abstract protected function writeAllFilesToDiskAndZipThem($finalFilePointer): void;
 168  
 169      /**
 170       * @return string The file path where the data for the given sheet will be stored
 171       */
 172      private function getWorksheetFilePath(Sheet $sheet): string
 173      {
 174          $sheetsContentTempFolder = $this->fileSystemHelper->getSheetsContentTempFolder();
 175  
 176          return $sheetsContentTempFolder.\DIRECTORY_SEPARATOR.'sheet'.(1 + $sheet->getIndex()).'.xml';
 177      }
 178  
 179      /**
 180       * Deletes the root folder created in the temp folder and all its contents.
 181       */
 182      private function cleanupTempFolder(): void
 183      {
 184          $rootFolder = $this->fileSystemHelper->getRootFolder();
 185          $this->fileSystemHelper->deleteFolderRecursively($rootFolder);
 186      }
 187  
 188      /**
 189       * Creates a new sheet in the workbook. The current sheet remains unchanged.
 190       *
 191       * @return Worksheet The created sheet
 192       *
 193       * @throws \OpenSpout\Common\Exception\IOException If unable to open the sheet for writing
 194       */
 195      private function addNewSheet(): Worksheet
 196      {
 197          $worksheets = $this->getWorksheets();
 198  
 199          $newSheetIndex = \count($worksheets);
 200          $sheetManager = new SheetManager(StringHelper::factory());
 201          $sheet = new Sheet($newSheetIndex, $this->workbook->getInternalId(), $sheetManager);
 202  
 203          $worksheetFilePath = $this->getWorksheetFilePath($sheet);
 204          $worksheet = new Worksheet($worksheetFilePath, $sheet);
 205  
 206          $this->worksheetManager->startSheet($worksheet);
 207  
 208          $worksheets[] = $worksheet;
 209          $this->workbook->setWorksheets($worksheets);
 210  
 211          return $worksheet;
 212      }
 213  
 214      private function setCurrentWorksheet(Worksheet $worksheet): void
 215      {
 216          $this->currentWorksheet = $worksheet;
 217      }
 218  
 219      /**
 220       * Returns the worksheet associated to the given external sheet.
 221       *
 222       * @return null|Worksheet the worksheet associated to the given external sheet or null if not found
 223       */
 224      private function getWorksheetFromExternalSheet(Sheet $sheet): ?Worksheet
 225      {
 226          $worksheetFound = null;
 227  
 228          foreach ($this->getWorksheets() as $worksheet) {
 229              if ($worksheet->getExternalSheet() === $sheet) {
 230                  $worksheetFound = $worksheet;
 231  
 232                  break;
 233              }
 234          }
 235  
 236          return $worksheetFound;
 237      }
 238  
 239      /**
 240       * @return bool whether the current worksheet has reached the maximum number of rows per sheet
 241       */
 242      private function hasCurrentWorksheetReachedMaxRows(): bool
 243      {
 244          $currentWorksheet = $this->getCurrentWorksheet();
 245  
 246          return $currentWorksheet->getLastWrittenRowIndex() >= $this->getMaxRowsPerWorksheet();
 247      }
 248  
 249      /**
 250       * Adds a row to the given sheet.
 251       *
 252       * @param Worksheet $worksheet Worksheet to write the row to
 253       * @param Row       $row       The row to be added
 254       *
 255       * @throws IOException
 256       * @throws \OpenSpout\Common\Exception\InvalidArgumentException
 257       */
 258      private function addRowToWorksheet(Worksheet $worksheet, Row $row): void
 259      {
 260          $this->applyDefaultRowStyle($row);
 261          $this->worksheetManager->addRow($worksheet, $row);
 262  
 263          // update max num columns for the worksheet
 264          $currentMaxNumColumns = $worksheet->getMaxNumColumns();
 265          $cellsCount = $row->getNumCells();
 266          $worksheet->setMaxNumColumns(max($currentMaxNumColumns, $cellsCount));
 267      }
 268  
 269      private function applyDefaultRowStyle(Row $row): void
 270      {
 271          $mergedStyle = $this->styleMerger->merge(
 272              $row->getStyle(),
 273              $this->options->DEFAULT_ROW_STYLE
 274          );
 275          $row->setStyle($mergedStyle);
 276      }
 277  
 278      /**
 279       * Closes all workbook's associated sheets.
 280       */
 281      private function closeAllWorksheets(): void
 282      {
 283          $worksheets = $this->getWorksheets();
 284  
 285          foreach ($worksheets as $worksheet) {
 286              $this->worksheetManager->close($worksheet);
 287          }
 288      }
 289  }