Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

   1  <?php
   2  
   3  namespace Box\Spout\Reader\Wrapper;
   4  
   5  /**
   6   * Class XMLReader
   7   * Wrapper around the built-in XMLReader
   8   * @see \XMLReader
   9   */
  10  class XMLReader extends \XMLReader
  11  {
  12      use XMLInternalErrorsHelper;
  13  
  14      const ZIP_WRAPPER = 'zip://';
  15  
  16      /**
  17       * Opens the XML Reader to read a file located inside a ZIP file.
  18       *
  19       * @param string $zipFilePath Path to the ZIP file
  20       * @param string $fileInsideZipPath Relative or absolute path of the file inside the zip
  21       * @return bool TRUE on success or FALSE on failure
  22       */
  23      public function openFileInZip($zipFilePath, $fileInsideZipPath)
  24      {
  25          $wasOpenSuccessful = false;
  26          $realPathURI = $this->getRealPathURIForFileInZip($zipFilePath, $fileInsideZipPath);
  27  
  28          // We need to check first that the file we are trying to read really exist because:
  29          //  - PHP emits a warning when trying to open a file that does not exist.
  30          //  - HHVM does not check if file exists within zip file (@link https://github.com/facebook/hhvm/issues/5779)
  31          if ($this->fileExistsWithinZip($realPathURI)) {
  32              $wasOpenSuccessful = $this->open($realPathURI, null, LIBXML_NONET);
  33          }
  34  
  35          return $wasOpenSuccessful;
  36      }
  37  
  38      /**
  39       * Returns the real path for the given path components.
  40       * This is useful to avoid issues on some Windows setup.
  41       *
  42       * @param string $zipFilePath Path to the ZIP file
  43       * @param string $fileInsideZipPath Relative or absolute path of the file inside the zip
  44       * @return string The real path URI
  45       */
  46      public function getRealPathURIForFileInZip($zipFilePath, $fileInsideZipPath)
  47      {
  48          // The file path should not start with a '/', otherwise it won't be found
  49          $fileInsideZipPathWithoutLeadingSlash = \ltrim($fileInsideZipPath, '/');
  50  
  51          return (self::ZIP_WRAPPER . \realpath($zipFilePath) . '#' . $fileInsideZipPathWithoutLeadingSlash);
  52      }
  53  
  54      /**
  55       * Returns whether the file at the given location exists
  56       *
  57       * @param string $zipStreamURI URI of a zip stream, e.g. "zip://file.zip#path/inside.xml"
  58       * @return bool TRUE if the file exists, FALSE otherwise
  59       */
  60      protected function fileExistsWithinZip($zipStreamURI)
  61      {
  62          $doesFileExists = false;
  63  
  64          $pattern = '/zip:\/\/([^#]+)#(.*)/';
  65          if (\preg_match($pattern, $zipStreamURI, $matches)) {
  66              $zipFilePath = $matches[1];
  67              $innerFilePath = $matches[2];
  68  
  69              $zip = new \ZipArchive();
  70              if ($zip->open($zipFilePath) === true) {
  71                  $doesFileExists = ($zip->locateName($innerFilePath) !== false);
  72                  $zip->close();
  73              }
  74          }
  75  
  76          return $doesFileExists;
  77      }
  78  
  79      /**
  80       * Move to next node in document
  81       * @see \XMLReader::read
  82       *
  83       * @throws \Box\Spout\Reader\Exception\XMLProcessingException If an error/warning occurred
  84       * @return bool TRUE on success or FALSE on failure
  85       */
  86      public function read()
  87      {
  88          $this->useXMLInternalErrors();
  89  
  90          $wasReadSuccessful = parent::read();
  91  
  92          $this->resetXMLInternalErrorsSettingAndThrowIfXMLErrorOccured();
  93  
  94          return $wasReadSuccessful;
  95      }
  96  
  97      /**
  98       * Read until the element with the given name is found, or the end of the file.
  99       *
 100       * @param string $nodeName Name of the node to find
 101       * @throws \Box\Spout\Reader\Exception\XMLProcessingException If an error/warning occurred
 102       * @return bool TRUE on success or FALSE on failure
 103       */
 104      public function readUntilNodeFound($nodeName)
 105      {
 106          do {
 107              $wasReadSuccessful = $this->read();
 108              $isNotPositionedOnStartingNode = !$this->isPositionedOnStartingNode($nodeName);
 109          } while ($wasReadSuccessful && $isNotPositionedOnStartingNode);
 110  
 111          return $wasReadSuccessful;
 112      }
 113  
 114      /**
 115       * Move cursor to next node skipping all subtrees
 116       * @see \XMLReader::next
 117       *
 118       * @param string|null $localName The name of the next node to move to
 119       * @throws \Box\Spout\Reader\Exception\XMLProcessingException If an error/warning occurred
 120       * @return bool TRUE on success or FALSE on failure
 121       */
 122      public function next($localName = null)
 123      {
 124          $this->useXMLInternalErrors();
 125  
 126          $wasNextSuccessful = parent::next($localName);
 127  
 128          $this->resetXMLInternalErrorsSettingAndThrowIfXMLErrorOccured();
 129  
 130          return $wasNextSuccessful;
 131      }
 132  
 133      /**
 134       * @param string $nodeName
 135       * @return bool Whether the XML Reader is currently positioned on the starting node with given name
 136       */
 137      public function isPositionedOnStartingNode($nodeName)
 138      {
 139          return $this->isPositionedOnNode($nodeName, self::ELEMENT);
 140      }
 141  
 142      /**
 143       * @param string $nodeName
 144       * @return bool Whether the XML Reader is currently positioned on the ending node with given name
 145       */
 146      public function isPositionedOnEndingNode($nodeName)
 147      {
 148          return $this->isPositionedOnNode($nodeName, self::END_ELEMENT);
 149      }
 150  
 151      /**
 152       * @param string $nodeName
 153       * @param int $nodeType
 154       * @return bool Whether the XML Reader is currently positioned on the node with given name and type
 155       */
 156      private function isPositionedOnNode($nodeName, $nodeType)
 157      {
 158          // In some cases, the node has a prefix (for instance, "<sheet>" can also be "<x:sheet>").
 159          // So if the given node name does not have a prefix, we need to look at the unprefixed name ("localName").
 160          // @see https://github.com/box/spout/issues/233
 161          $hasPrefix = (\strpos($nodeName, ':') !== false);
 162          $currentNodeName = ($hasPrefix) ? $this->name : $this->localName;
 163  
 164          return ($this->nodeType === $nodeType && $currentNodeName === $nodeName);
 165      }
 166  
 167      /**
 168       * @return string The name of the current node, un-prefixed
 169       */
 170      public function getCurrentNodeName()
 171      {
 172          return $this->localName;
 173      }
 174  }