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 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
   4  
   5  use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
   6  use PhpOffice\PhpSpreadsheet\Reader\DefaultReadFilter;
   7  use PhpOffice\PhpSpreadsheet\Reader\IReadFilter;
   8  use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
   9  use SimpleXMLElement;
  10  
  11  class ColumnAndRowAttributes extends BaseParserClass
  12  {
  13      /** @var Worksheet */
  14      private $worksheet;
  15  
  16      /** @var ?SimpleXMLElement */
  17      private $worksheetXml;
  18  
  19      public function __construct(Worksheet $workSheet, ?SimpleXMLElement $worksheetXml = null)
  20      {
  21          $this->worksheet = $workSheet;
  22          $this->worksheetXml = $worksheetXml;
  23      }
  24  
  25      /**
  26       * Set Worksheet column attributes by attributes array passed.
  27       *
  28       * @param string $columnAddress A, B, ... DX, ...
  29       * @param array $columnAttributes array of attributes (indexes are attribute name, values are value)
  30       *                               'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'width', ... ?
  31       */
  32      private function setColumnAttributes($columnAddress, array $columnAttributes): void
  33      {
  34          if (isset($columnAttributes['xfIndex'])) {
  35              $this->worksheet->getColumnDimension($columnAddress)->setXfIndex($columnAttributes['xfIndex']);
  36          }
  37          if (isset($columnAttributes['visible'])) {
  38              $this->worksheet->getColumnDimension($columnAddress)->setVisible($columnAttributes['visible']);
  39          }
  40          if (isset($columnAttributes['collapsed'])) {
  41              $this->worksheet->getColumnDimension($columnAddress)->setCollapsed($columnAttributes['collapsed']);
  42          }
  43          if (isset($columnAttributes['outlineLevel'])) {
  44              $this->worksheet->getColumnDimension($columnAddress)->setOutlineLevel($columnAttributes['outlineLevel']);
  45          }
  46          if (isset($columnAttributes['width'])) {
  47              $this->worksheet->getColumnDimension($columnAddress)->setWidth($columnAttributes['width']);
  48          }
  49      }
  50  
  51      /**
  52       * Set Worksheet row attributes by attributes array passed.
  53       *
  54       * @param int $rowNumber 1, 2, 3, ... 99, ...
  55       * @param array $rowAttributes array of attributes (indexes are attribute name, values are value)
  56       *                               'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'rowHeight', ... ?
  57       */
  58      private function setRowAttributes($rowNumber, array $rowAttributes): void
  59      {
  60          if (isset($rowAttributes['xfIndex'])) {
  61              $this->worksheet->getRowDimension($rowNumber)->setXfIndex($rowAttributes['xfIndex']);
  62          }
  63          if (isset($rowAttributes['visible'])) {
  64              $this->worksheet->getRowDimension($rowNumber)->setVisible($rowAttributes['visible']);
  65          }
  66          if (isset($rowAttributes['collapsed'])) {
  67              $this->worksheet->getRowDimension($rowNumber)->setCollapsed($rowAttributes['collapsed']);
  68          }
  69          if (isset($rowAttributes['outlineLevel'])) {
  70              $this->worksheet->getRowDimension($rowNumber)->setOutlineLevel($rowAttributes['outlineLevel']);
  71          }
  72          if (isset($rowAttributes['rowHeight'])) {
  73              $this->worksheet->getRowDimension($rowNumber)->setRowHeight($rowAttributes['rowHeight']);
  74          }
  75      }
  76  
  77      public function load(?IReadFilter $readFilter = null, bool $readDataOnly = false): void
  78      {
  79          if ($this->worksheetXml === null) {
  80              return;
  81          }
  82  
  83          $columnsAttributes = [];
  84          $rowsAttributes = [];
  85          if (isset($this->worksheetXml->cols)) {
  86              $columnsAttributes = $this->readColumnAttributes($this->worksheetXml->cols, $readDataOnly);
  87          }
  88  
  89          if ($this->worksheetXml->sheetData && $this->worksheetXml->sheetData->row) {
  90              $rowsAttributes = $this->readRowAttributes($this->worksheetXml->sheetData->row, $readDataOnly);
  91          }
  92  
  93          if ($readFilter !== null && get_class($readFilter) === DefaultReadFilter::class) {
  94              $readFilter = null;
  95          }
  96  
  97          // set columns/rows attributes
  98          $columnsAttributesAreSet = [];
  99          foreach ($columnsAttributes as $columnCoordinate => $columnAttributes) {
 100              if (
 101                  $readFilter === null ||
 102                  !$this->isFilteredColumn($readFilter, $columnCoordinate, $rowsAttributes)
 103              ) {
 104                  if (!isset($columnsAttributesAreSet[$columnCoordinate])) {
 105                      $this->setColumnAttributes($columnCoordinate, $columnAttributes);
 106                      $columnsAttributesAreSet[$columnCoordinate] = true;
 107                  }
 108              }
 109          }
 110  
 111          $rowsAttributesAreSet = [];
 112          foreach ($rowsAttributes as $rowCoordinate => $rowAttributes) {
 113              if (
 114                  $readFilter === null ||
 115                  !$this->isFilteredRow($readFilter, $rowCoordinate, $columnsAttributes)
 116              ) {
 117                  if (!isset($rowsAttributesAreSet[$rowCoordinate])) {
 118                      $this->setRowAttributes($rowCoordinate, $rowAttributes);
 119                      $rowsAttributesAreSet[$rowCoordinate] = true;
 120                  }
 121              }
 122          }
 123      }
 124  
 125      private function isFilteredColumn(IReadFilter $readFilter, string $columnCoordinate, array $rowsAttributes): bool
 126      {
 127          foreach ($rowsAttributes as $rowCoordinate => $rowAttributes) {
 128              if (!$readFilter->readCell($columnCoordinate, $rowCoordinate, $this->worksheet->getTitle())) {
 129                  return true;
 130              }
 131          }
 132  
 133          return false;
 134      }
 135  
 136      private function readColumnAttributes(SimpleXMLElement $worksheetCols, bool $readDataOnly): array
 137      {
 138          $columnAttributes = [];
 139  
 140          foreach ($worksheetCols->col as $column) {
 141              $startColumn = Coordinate::stringFromColumnIndex((int) $column['min']);
 142              $endColumn = Coordinate::stringFromColumnIndex((int) $column['max']);
 143              ++$endColumn;
 144              for ($columnAddress = $startColumn; $columnAddress !== $endColumn; ++$columnAddress) {
 145                  $columnAttributes[$columnAddress] = $this->readColumnRangeAttributes($column, $readDataOnly);
 146  
 147                  if ((int) ($column['max']) == 16384) {
 148                      break;
 149                  }
 150              }
 151          }
 152  
 153          return $columnAttributes;
 154      }
 155  
 156      private function readColumnRangeAttributes(SimpleXMLElement $column, bool $readDataOnly): array
 157      {
 158          $columnAttributes = [];
 159  
 160          if ($column['style'] && !$readDataOnly) {
 161              $columnAttributes['xfIndex'] = (int) $column['style'];
 162          }
 163          if (self::boolean($column['hidden'])) {
 164              $columnAttributes['visible'] = false;
 165          }
 166          if (self::boolean($column['collapsed'])) {
 167              $columnAttributes['collapsed'] = true;
 168          }
 169          if (((int) $column['outlineLevel']) > 0) {
 170              $columnAttributes['outlineLevel'] = (int) $column['outlineLevel'];
 171          }
 172          $columnAttributes['width'] = (float) $column['width'];
 173  
 174          return $columnAttributes;
 175      }
 176  
 177      private function isFilteredRow(IReadFilter $readFilter, int $rowCoordinate, array $columnsAttributes): bool
 178      {
 179          foreach ($columnsAttributes as $columnCoordinate => $columnAttributes) {
 180              if (!$readFilter->readCell($columnCoordinate, $rowCoordinate, $this->worksheet->getTitle())) {
 181                  return true;
 182              }
 183          }
 184  
 185          return false;
 186      }
 187  
 188      private function readRowAttributes(SimpleXMLElement $worksheetRow, bool $readDataOnly): array
 189      {
 190          $rowAttributes = [];
 191  
 192          foreach ($worksheetRow as $row) {
 193              if ($row['ht'] && !$readDataOnly) {
 194                  $rowAttributes[(int) $row['r']]['rowHeight'] = (float) $row['ht'];
 195              }
 196              if (self::boolean($row['hidden'])) {
 197                  $rowAttributes[(int) $row['r']]['visible'] = false;
 198              }
 199              if (self::boolean($row['collapsed'])) {
 200                  $rowAttributes[(int) $row['r']]['collapsed'] = true;
 201              }
 202              if ((int) $row['outlineLevel'] > 0) {
 203                  $rowAttributes[(int) $row['r']]['outlineLevel'] = (int) $row['outlineLevel'];
 204              }
 205              if ($row['s'] && !$readDataOnly) {
 206                  $rowAttributes[(int) $row['r']]['xfIndex'] = (int) $row['s'];
 207              }
 208          }
 209  
 210          return $rowAttributes;
 211      }
 212  }