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.

Differences Between: [Versions 310 and 401] [Versions 39 and 401]

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