Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401]

   1  <?php
   2  
   3  namespace Box\Spout\Reader\XLSX\Manager;
   4  
   5  use Box\Spout\Common\Exception\IOException;
   6  use Box\Spout\Reader\Wrapper\XMLReader;
   7  use Box\Spout\Reader\XLSX\Creator\InternalEntityFactory;
   8  
   9  /**
  10   * Class WorkbookRelationshipsManager
  11   * This class manages the workbook relationships defined in the associated XML file
  12   */
  13  class WorkbookRelationshipsManager
  14  {
  15      const BASE_PATH = 'xl/';
  16  
  17      /** Path of workbook relationships XML file inside the XLSX file */
  18      const WORKBOOK_RELS_XML_FILE_PATH = 'xl/_rels/workbook.xml.rels';
  19  
  20      /** Relationships types */
  21      const RELATIONSHIP_TYPE_SHARED_STRINGS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings';
  22      const RELATIONSHIP_TYPE_STYLES = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles';
  23      const RELATIONSHIP_TYPE_WORKSHEET = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet';
  24  
  25      /** Nodes and attributes used to find relevant information in the workbook relationships XML file */
  26      const XML_NODE_RELATIONSHIP = 'Relationship';
  27      const XML_ATTRIBUTE_TYPE = 'Type';
  28      const XML_ATTRIBUTE_TARGET = 'Target';
  29  
  30      /** @var string Path of the XLSX file being read */
  31      private $filePath;
  32  
  33      /** @var InternalEntityFactory Factory to create entities */
  34      private $entityFactory;
  35  
  36      /** @var array Cache of the already read workbook relationships: [TYPE] => [FILE_NAME] */
  37      private $cachedWorkbookRelationships;
  38  
  39      /**
  40       * @param string $filePath Path of the XLSX file being read
  41       * @param InternalEntityFactory $entityFactory Factory to create entities
  42       */
  43      public function __construct($filePath, $entityFactory)
  44      {
  45          $this->filePath = $filePath;
  46          $this->entityFactory = $entityFactory;
  47      }
  48  
  49      /**
  50       * @return string The path of the shared string XML file
  51       */
  52      public function getSharedStringsXMLFilePath()
  53      {
  54          $workbookRelationships = $this->getWorkbookRelationships();
  55          $sharedStringsXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS];
  56  
  57          // the file path can be relative (e.g. "styles.xml") or absolute (e.g. "/xl/styles.xml")
  58          $doesContainBasePath = (strpos($sharedStringsXMLFilePath, self::BASE_PATH) !== false);
  59          if (!$doesContainBasePath) {
  60              // make sure we return an absolute file path
  61              $sharedStringsXMLFilePath = self::BASE_PATH . $sharedStringsXMLFilePath;
  62          }
  63  
  64          return $sharedStringsXMLFilePath;
  65      }
  66  
  67      /**
  68       * @return bool Whether the XLSX file contains a shared string XML file
  69       */
  70      public function hasSharedStringsXMLFile()
  71      {
  72          $workbookRelationships = $this->getWorkbookRelationships();
  73  
  74          return isset($workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS]);
  75      }
  76  
  77      /**
  78       * @return string|null The path of the styles XML file
  79       */
  80      public function getStylesXMLFilePath()
  81      {
  82          $workbookRelationships = $this->getWorkbookRelationships();
  83          $stylesXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_STYLES];
  84  
  85          // the file path can be relative (e.g. "styles.xml") or absolute (e.g. "/xl/styles.xml")
  86          $doesContainBasePath = (strpos($stylesXMLFilePath, self::BASE_PATH) !== false);
  87          if (!$doesContainBasePath) {
  88              // make sure we return a full path
  89              $stylesXMLFilePath = self::BASE_PATH . $stylesXMLFilePath;
  90          }
  91  
  92          return $stylesXMLFilePath;
  93      }
  94  
  95      /**
  96       * Reads the workbook.xml.rels and extracts the filename associated to the different types.
  97       * It caches the result so that the file is read only once.
  98       *
  99       * @throws \Box\Spout\Common\Exception\IOException If workbook.xml.rels can't be read
 100       * @return array
 101       */
 102      private function getWorkbookRelationships()
 103      {
 104          if (!isset($this->cachedWorkbookRelationships)) {
 105              $xmlReader = $this->entityFactory->createXMLReader();
 106  
 107              if ($xmlReader->openFileInZip($this->filePath, self::WORKBOOK_RELS_XML_FILE_PATH) === false) {
 108                  throw new IOException('Could not open "' . self::WORKBOOK_RELS_XML_FILE_PATH . '".');
 109              }
 110  
 111              $this->cachedWorkbookRelationships = [];
 112  
 113              while ($xmlReader->readUntilNodeFound(self::XML_NODE_RELATIONSHIP)) {
 114                  $this->processWorkbookRelationship($xmlReader);
 115              }
 116          }
 117  
 118          return $this->cachedWorkbookRelationships;
 119      }
 120  
 121      /**
 122       * Extracts and store the data of the current workbook relationship.
 123       *
 124       * @param XMLReader $xmlReader
 125       * @return void
 126       */
 127      private function processWorkbookRelationship($xmlReader)
 128      {
 129          $type = $xmlReader->getAttribute(self::XML_ATTRIBUTE_TYPE);
 130          $target = $xmlReader->getAttribute(self::XML_ATTRIBUTE_TARGET);
 131  
 132          // @NOTE: if a type is defined more than once, we overwrite the previous value
 133          // To be changed if we want to get the file paths of sheet XML files for instance.
 134          $this->cachedWorkbookRelationships[$type] = $target;
 135      }
 136  }