Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 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 310 and 311] [Versions 310 and 400] [Versions 310 and 401]

   1  <?php
   2  /*
   3   * Copyright 2016-2017 MongoDB, Inc.
   4   *
   5   * Licensed under the Apache License, Version 2.0 (the "License");
   6   * you may not use this file except in compliance with the License.
   7   * You may obtain a copy of the License at
   8   *
   9   *   http://www.apache.org/licenses/LICENSE-2.0
  10   *
  11   * Unless required by applicable law or agreed to in writing, software
  12   * distributed under the License is distributed on an "AS IS" BASIS,
  13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14   * See the License for the specific language governing permissions and
  15   * limitations under the License.
  16   */
  17  
  18  namespace MongoDB\GridFS;
  19  
  20  use MongoDB\Collection;
  21  use MongoDB\Driver\Cursor;
  22  use MongoDB\Driver\Manager;
  23  use MongoDB\Driver\ReadPreference;
  24  use MongoDB\Exception\InvalidArgumentException;
  25  use MongoDB\UpdateResult;
  26  use stdClass;
  27  use function abs;
  28  use function sprintf;
  29  
  30  /**
  31   * CollectionWrapper abstracts the GridFS files and chunks collections.
  32   *
  33   * @internal
  34   */
  35  class CollectionWrapper
  36  {
  37      /** @var string */
  38      private $bucketName;
  39  
  40      /** @var Collection */
  41      private $chunksCollection;
  42  
  43      /** @var string */
  44      private $databaseName;
  45  
  46      /** @var boolean */
  47      private $checkedIndexes = false;
  48  
  49      /** @var Collection */
  50      private $filesCollection;
  51  
  52      /**
  53       * Constructs a GridFS collection wrapper.
  54       *
  55       * @see Collection::__construct() for supported options
  56       * @param Manager $manager           Manager instance from the driver
  57       * @param string  $databaseName      Database name
  58       * @param string  $bucketName        Bucket name
  59       * @param array   $collectionOptions Collection options
  60       * @throws InvalidArgumentException
  61       */
  62      public function __construct(Manager $manager, $databaseName, $bucketName, array $collectionOptions = [])
  63      {
  64          $this->databaseName = (string) $databaseName;
  65          $this->bucketName = (string) $bucketName;
  66  
  67          $this->filesCollection = new Collection($manager, $databaseName, sprintf('%s.files', $bucketName), $collectionOptions);
  68          $this->chunksCollection = new Collection($manager, $databaseName, sprintf('%s.chunks', $bucketName), $collectionOptions);
  69      }
  70  
  71      /**
  72       * Deletes all GridFS chunks for a given file ID.
  73       *
  74       * @param mixed $id
  75       */
  76      public function deleteChunksByFilesId($id)
  77      {
  78          $this->chunksCollection->deleteMany(['files_id' => $id]);
  79      }
  80  
  81      /**
  82       * Deletes a GridFS file and related chunks by ID.
  83       *
  84       * @param mixed $id
  85       */
  86      public function deleteFileAndChunksById($id)
  87      {
  88          $this->filesCollection->deleteOne(['_id' => $id]);
  89          $this->chunksCollection->deleteMany(['files_id' => $id]);
  90      }
  91  
  92      /**
  93       * Drops the GridFS files and chunks collections.
  94       */
  95      public function dropCollections()
  96      {
  97          $this->filesCollection->drop(['typeMap' => []]);
  98          $this->chunksCollection->drop(['typeMap' => []]);
  99      }
 100  
 101      /**
 102       * Finds GridFS chunk documents for a given file ID and optional offset.
 103       *
 104       * @param mixed   $id        File ID
 105       * @param integer $fromChunk Starting chunk (inclusive)
 106       * @return Cursor
 107       */
 108      public function findChunksByFileId($id, $fromChunk = 0)
 109      {
 110          return $this->chunksCollection->find(
 111              [
 112                  'files_id' => $id,
 113                  'n' => ['$gte' => $fromChunk],
 114              ],
 115              [
 116                  'sort' => ['n' => 1],
 117                  'typeMap' => ['root' => 'stdClass'],
 118              ]
 119          );
 120      }
 121  
 122      /**
 123       * Finds a GridFS file document for a given filename and revision.
 124       *
 125       * Revision numbers are defined as follows:
 126       *
 127       *  * 0 = the original stored file
 128       *  * 1 = the first revision
 129       *  * 2 = the second revision
 130       *  * etc…
 131       *  * -2 = the second most recent revision
 132       *  * -1 = the most recent revision
 133       *
 134       * @see Bucket::downloadToStreamByName()
 135       * @see Bucket::openDownloadStreamByName()
 136       * @param string  $filename
 137       * @param integer $revision
 138       * @return stdClass|null
 139       */
 140      public function findFileByFilenameAndRevision($filename, $revision)
 141      {
 142          $filename = (string) $filename;
 143          $revision = (integer) $revision;
 144  
 145          if ($revision < 0) {
 146              $skip = abs($revision) - 1;
 147              $sortOrder = -1;
 148          } else {
 149              $skip = $revision;
 150              $sortOrder = 1;
 151          }
 152  
 153          return $this->filesCollection->findOne(
 154              ['filename' => $filename],
 155              [
 156                  'skip' => $skip,
 157                  'sort' => ['uploadDate' => $sortOrder],
 158                  'typeMap' => ['root' => 'stdClass'],
 159              ]
 160          );
 161      }
 162  
 163      /**
 164       * Finds a GridFS file document for a given ID.
 165       *
 166       * @param mixed $id
 167       * @return stdClass|null
 168       */
 169      public function findFileById($id)
 170      {
 171          return $this->filesCollection->findOne(
 172              ['_id' => $id],
 173              ['typeMap' => ['root' => 'stdClass']]
 174          );
 175      }
 176  
 177      /**
 178       * Finds documents from the GridFS bucket's files collection.
 179       *
 180       * @see Find::__construct() for supported options
 181       * @param array|object $filter  Query by which to filter documents
 182       * @param array        $options Additional options
 183       * @return Cursor
 184       */
 185      public function findFiles($filter, array $options = [])
 186      {
 187          return $this->filesCollection->find($filter, $options);
 188      }
 189  
 190      /**
 191       * Finds a single document from the GridFS bucket's files collection.
 192       *
 193       * @param array|object $filter  Query by which to filter documents
 194       * @param array        $options Additional options
 195       * @return array|object|null
 196       */
 197      public function findOneFile($filter, array $options = [])
 198      {
 199          return $this->filesCollection->findOne($filter, $options);
 200      }
 201  
 202      /**
 203       * Return the bucket name.
 204       *
 205       * @return string
 206       */
 207      public function getBucketName()
 208      {
 209          return $this->bucketName;
 210      }
 211  
 212      /**
 213       * Return the chunks collection.
 214       *
 215       * @return Collection
 216       */
 217      public function getChunksCollection()
 218      {
 219          return $this->chunksCollection;
 220      }
 221  
 222      /**
 223       * Return the database name.
 224       *
 225       * @return string
 226       */
 227      public function getDatabaseName()
 228      {
 229          return $this->databaseName;
 230      }
 231  
 232      /**
 233       * Return the files collection.
 234       *
 235       * @return Collection
 236       */
 237      public function getFilesCollection()
 238      {
 239          return $this->filesCollection;
 240      }
 241  
 242      /**
 243       * Inserts a document into the chunks collection.
 244       *
 245       * @param array|object $chunk Chunk document
 246       */
 247      public function insertChunk($chunk)
 248      {
 249          if (! $this->checkedIndexes) {
 250              $this->ensureIndexes();
 251          }
 252  
 253          $this->chunksCollection->insertOne($chunk);
 254      }
 255  
 256      /**
 257       * Inserts a document into the files collection.
 258       *
 259       * The file document should be inserted after all chunks have been inserted.
 260       *
 261       * @param array|object $file File document
 262       */
 263      public function insertFile($file)
 264      {
 265          if (! $this->checkedIndexes) {
 266              $this->ensureIndexes();
 267          }
 268  
 269          $this->filesCollection->insertOne($file);
 270      }
 271  
 272      /**
 273       * Updates the filename field in the file document for a given ID.
 274       *
 275       * @param mixed  $id
 276       * @param string $filename
 277       * @return UpdateResult
 278       */
 279      public function updateFilenameForId($id, $filename)
 280      {
 281          return $this->filesCollection->updateOne(
 282              ['_id' => $id],
 283              ['$set' => ['filename' => (string) $filename]]
 284          );
 285      }
 286  
 287      /**
 288       * Create an index on the chunks collection if it does not already exist.
 289       */
 290      private function ensureChunksIndex()
 291      {
 292          foreach ($this->chunksCollection->listIndexes() as $index) {
 293              if ($index->isUnique() && $index->getKey() === ['files_id' => 1, 'n' => 1]) {
 294                  return;
 295              }
 296          }
 297  
 298          $this->chunksCollection->createIndex(['files_id' => 1, 'n' => 1], ['unique' => true]);
 299      }
 300  
 301      /**
 302       * Create an index on the files collection if it does not already exist.
 303       */
 304      private function ensureFilesIndex()
 305      {
 306          foreach ($this->filesCollection->listIndexes() as $index) {
 307              if ($index->getKey() === ['filename' => 1, 'uploadDate' => 1]) {
 308                  return;
 309              }
 310          }
 311  
 312          $this->filesCollection->createIndex(['filename' => 1, 'uploadDate' => 1]);
 313      }
 314  
 315      /**
 316       * Ensure indexes on the files and chunks collections exist.
 317       *
 318       * This method is called once before the first write operation on a GridFS
 319       * bucket. Indexes are only be created if the files collection is empty.
 320       */
 321      private function ensureIndexes()
 322      {
 323          if ($this->checkedIndexes) {
 324              return;
 325          }
 326  
 327          $this->checkedIndexes = true;
 328  
 329          if (! $this->isFilesCollectionEmpty()) {
 330              return;
 331          }
 332  
 333          $this->ensureFilesIndex();
 334          $this->ensureChunksIndex();
 335      }
 336  
 337      /**
 338       * Returns whether the files collection is empty.
 339       *
 340       * @return boolean
 341       */
 342      private function isFilesCollectionEmpty()
 343      {
 344          return null === $this->filesCollection->findOne([], [
 345              'readPreference' => new ReadPreference(ReadPreference::RP_PRIMARY),
 346              'projection' => ['_id' => 1],
 347              'typeMap' => [],
 348          ]);
 349      }
 350  }