text: <?php
  
  namespace League\Flysystem\ZipArchive;
  
  use League\Flysystem\Adapter\AbstractAdapter;
  use League\Flysystem\Adapter\Polyfill\NotSupportingVisibilityTrait;
  use League\Flysystem\Adapter\Polyfill\StreamedCopyTrait;
  use League\Flysystem\Adapter\Polyfill\StreamedWritingTrait;
  use League\Flysystem\Config;
  use League\Flysystem\Util;
  use LogicException;
  use ZipArchive;
  
  class ZipArchiveAdapter extends AbstractAdapter
  {
      use StreamedWritingTrait;
      use StreamedCopyTrait;
      use NotSupportingVisibilityTrait;
  
      /**
       * @var array
       */
      protected static $resultMap = [
          'size'  => 'size',
          'mtime' => 'timestamp',
          'name'  => 'path',
      ];
  
      /**
       * @var ZipArchive
       */
      protected $archive;
  
      /**
       * @param             $location
       * @param ZipArchive  $archive
       * @param string|null $prefix
       */
      public function __construct($location, ZipArchive $archive = null, $prefix = null)
      {
          $this->setArchive($archive ?: new ZipArchive());
          $this->openArchive($location);
          $this->setPathPrefix($prefix);
      }
  
      /**
       * Re-open an archive to ensure persistence.
       */
      protected function reopenArchive()
      {
          $path = $this->archive->filename;
          $this->archive->close();
          $this->openArchive($path);
      }
  
      /**
       * ZipArchive setter.
       *
       * @param ZipArchive $archive
       */
      public function setArchive(ZipArchive $archive)
      {
          $this->archive = $archive;
      }
  
      /**
       * Get the used ZipArchive.
       *
       * @return ZipArchive
       */
      public function getArchive()
      {
          return $this->archive;
      }
  
      /**
       * Open a zip file.
       *
       * @param $location
       */
      public function openArchive($location)
      {
          $location = str_replace('/', DIRECTORY_SEPARATOR, $location);
  
          if (($response = $this->archive->open($location, ZipArchive::CREATE)) !== true) {
              throw new LogicException('Could not open zip archive at:'.$location.', error: '.$response);
          }
      }
  
      /**
       * {@inheritdoc}
       */
      public function write($path, $contents, Config $config)
      {
          $location = $this->applyPathPrefix($path);
          $dirname = Util::dirname($path);
  
          if (! empty($dirname) && ! $this->has($dirname)) {
              $this->createDir($dirname, $config);
          }
  
          if (! $this->archive->addFromString($location, $contents)) {
              return false;
          }
  
          $result = compact('path', 'contents');
  
          if ($config && $config->get('visibility')) {
              throw new LogicException(get_class($this).' does not support visibility settings.');
          }
  
          return $result;
      }
  
      /**
       * {@inheritdoc}
       */
      public function update($path, $contents, Config $config)
      {
          $this->delete($path);
  
          return $this->write($path, $contents, $config);
      }
  
      /**
       * {@inheritdoc}
       */
      public function rename($path, $newpath)
      {
          $source = $this->applyPathPrefix($path);
          $destination = $this->applyPathPrefix($newpath);
  
          return $this->archive->renameName($source, $destination);
      }
  
      /**
       * {@inheritdoc}
       */
      public function delete($path)
      {
          $location = $this->applyPathPrefix($path);
  
          return $this->archive->deleteName($location);
      }
  
      /**
       * {@inheritdoc}
       */
      public function deleteDir($dirname)
      {
          // This is needed to ensure the right number of
          // files are set to the $numFiles property.
          $this->reopenArchive();
  
          $location = $this->applyPathPrefix($dirname);
          $path = Util::normalizePrefix($location, '/');
          $length = strlen($path);
  
          for ($i = 0; $i < $this->archive->numFiles; $i++) {
              $info = $this->archive->statIndex($i);
  
              if (substr($info['name'], 0, $length) === $path) {
                  $this->archive->deleteIndex($i);
              }
          }
  
          return $this->archive->deleteName($dirname);
      }
  
      /**
       * {@inheritdoc}
       */
      public function createDir($dirname, Config $config)
      {
          if (! $this->has($dirname)) {
              $location = $this->applyPathPrefix($dirname);
              $this->archive->addEmptyDir($location);
          }
  
          return ['path' => $dirname];
      }
  
      /**
       * {@inheritdoc}
       */
      public function has($path)
      {
          return $this->getMetadata($path);
      }
  
      /**
       * {@inheritdoc}
       */
      public function read($path)
      {
          $this->reopenArchive();
          $location = $this->applyPathPrefix($path);
  
          if (! $contents = $this->archive->getFromName($location)) {
              return false;
          }
  
          return compact('contents');
      }
  
      /**
       * {@inheritdoc}
       */
      public function readStream($path)
      {
          $this->reopenArchive();
          $location = $this->applyPathPrefix($path);
  
          if (! $stream = $this->archive->getStream($location)) {
              return false;
          }
  
          return compact('stream');
      }
  
      /**
       * {@inheritdoc}
       */
      public function listContents($dirname = '', $recursive = false)
      {
          $result = [];
  
          // This is needed to ensure the right number of
          // files are set to the $numFiles property.
          $this->reopenArchive();
  
          for ($i = 0; $i < $this->archive->numFiles; $i++) {
              if ($info = $this->archive->statIndex($i)) {
                  $result[] = $info;
              }
          }
  
          $pathPrefix = $this->getPathPrefix();
          $prefixLength = strlen($pathPrefix);
  
          return array_filter(array_map(function ($item) use ($pathPrefix, $prefixLength) {
              if ($pathPrefix && (substr($item['name'], 0, $prefixLength) !== $pathPrefix || $item['name'] === $pathPrefix)) {
                  return false;
              }
  
              return $this->normalizeObject($item);
          }, $result));
      }
  
      /**
       * {@inheritdoc}
       */
      public function getMetadata($path)
      {
          $location = $this->applyPathPrefix($path);
  
          if (! $info = $this->archive->statName($location)) {
  
              // Check if $location is a directory.
              if (! $info = $this->archive->statName($location . '/')) {
                  return false;
              }
          }
  
          return $this->normalizeObject($info);
      }
  
      /**
       * Normalize a zip response array.
       *
       * @param array $object
       *
       * @return array
       */
      protected function normalizeObject(array $object)
      {
          if (substr($object['name'], -1) === '/') {
              return [
                  'path' => $this->removePathPrefix(trim($object['name'], '/')),
                  'type' => 'dir',
              ];
          }
  
          $result = ['type' => 'file'];
          $normalised = Util::map($object, static::$resultMap);
          $normalised['path'] = $this->removePathPrefix($normalised['path']);
  
          return array_merge($result, $normalised);
      }
  
      /**
       * {@inheritdoc}
       */
      public function getSize($path)
      {
          return $this->getMetadata($path);
      }
  
      /**
       * {@inheritdoc}
       */
      public function getMimetype($path)
      {
          if (! $data = $this->read($path)) {
              return false;
          }
  
          $data['mimetype'] = Util::guessMimeType($path, $data['contents']);
  
          return $data;
      }
  
      /**
       * {@inheritdoc}
       */
      public function getTimestamp($path)
      {
          return $this->getMetadata($path);
      }
  }
  