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