foldershare-8.x-1.2/src/Entity/FolderShareTraits/OperationArchiveTrait.php

src/Entity/FolderShareTraits/OperationArchiveTrait.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
<?php
 
namespace Drupal\foldershare\Entity\FolderShareTraits;
 
use Drupal\foldershare\Settings;
use Drupal\foldershare\ManageFilenameExtensions;
use Drupal\foldershare\ManageFileSystem;
use Drupal\foldershare\Utilities\FileUtilities;
use Drupal\foldershare\Utilities\FormatUtilities;
use Drupal\foldershare\Entity\Exception\LockException;
use Drupal\foldershare\Entity\Exception\SystemException;
use Drupal\foldershare\Entity\Exception\ValidationException;
 
/**
 * Archive FolderShare entities to a FolderShare ZIP archive.
 *
 * This trait includes methods to archive one or more FolderShare
 * entities into a ZIP archive saved into a new FolderShare entity.
 *
 * <B>Internal trait</B>
 * This trait is internal to the FolderShare module and used to define
 * features of the FolderShare entity class. It is a mechanism to group
 * functionality to improve code management.
 *
 * @ingroup foldershare
 */
trait OperationArchiveTrait {
 
  /*---------------------------------------------------------------------
   *
   * Archive to FolderShare entity.
   *
   *---------------------------------------------------------------------*/
 
  /**
   * Archives the given root items to a new archive in the root list.
   *
   * A new ZIP archive is created in the root list and all of the given
   * items, and their children, recursively, are added to the archive.
   *
   * All items must be root items.
   *
   * <B>Process locks</B>
   * The root folder trees above the items added to the archive are locked
   * for exclusive use during the operation.
   *
   * <B>Post-operation hooks</B>
   * This method calls the "hook_foldershare_post_operation_add_files" hook.
   *
   * <B>Activity log</B>
   * This method posts a log message after the archive is created.
   *
   * @param \Drupal\foldershare\FolderShareInterface[] $items
   *   An array of root items that are to be included in a new archive
   *   added to the root list.
   *
   * @return \Drupal\foldershare\FolderShareInterface
   *   Returns the FolderShare entity for the new archive.
   *
   * @throws \Drupal\foldershare\Entity\Exception\LockException
   *   Thrown if an access lock on any item could not be acquired.
   * @throws \Drupal\foldershare\Entity\Exception\ValidationException
   *   Thrown if any item in the array are not root items.
   * @throws \Drupal\foldershare\Entity\Exception\SystemException
   *   Thrown if the archive file could not be created, such as if the
   *   temporary directory does not exist or is not writable, if a temporary
   *   file for the archive could not be created, or if any of the file
   *   children of this item could not be read and saved into the archive.
   */
  public static function archiveToRoot(array $items) {
    //
    // Validate.
    // ---------
    // ZIP extensions must be supported and all items must be root items.
    if (empty($items) === TRUE) {
      return NULL;
    }
 
    if (ManageFilenameExtensions::isZipExtensionAllowed() === FALSE) {
      throw new ValidationException(FormatUtilities::createFormattedMessage(
        t(
          '@method was called to create an archive type the site does not support.',
          [
            '@method' => __METHOD__,
          ]),
        t('The ZIP file type required for new archives is not an allowed file type for the site. Archive creation is therefore not supported.')));
    }
 
    $itemsToArchive = [];
    foreach ($items as $item) {
      if ($item !== NULL) {
        if ($item->isRootItem() === FALSE) {
          throw new ValidationException(FormatUtilities::createFormattedMessage(
            t(
              '@method was called with an item that is not a root item.',
              [
                '@method' => __METHOD__,
              ])));
        }
 
        $itemsToArchive[] = $item;
      }
    }
 
    if (empty($itemsToArchive) === TRUE) {
      return NULL;
    }
 
    //
    // Create archive in local temp storage.
    // -------------------------------------
    // Create a ZIP archive containing the given items.
    //
    // On completion, a ZIP archive exists on the local file system in a
    // temporary file. This throws an exception and deletes the archive on
    // an error.
    $archiveUri = self::createZipArchive($itemsToArchive);
 
    //
    // Create File entity.
    // -------------------
    // Create a new File entity from the local file. This also moves the
    // file into FolderShare's directory tree and gives it a numeric name.
    // The MIME type is also set, and the File is marked as permanent.
    // This throws an exception if the File could not be created.
    try {
      if (count($itemsToArchive) === 1) {
        // Add '.zip' to the item name.
        $archiveName = $itemsToArchive[0]->getName() . '.zip';
      }
      else {
        // Use a generic name.
        $archiveName = Settings::getNewZipArchiveName();
      }
 
      $archiveFile = self::createFileEntityFromLocalFile(
        $archiveUri,
        $archiveName);
    }
    catch (\Exception $e) {
      FileUtilities::unlink($archiveUri);
      throw $e;
    }
 
    //
    // Add the archive to the root list.
    // ---------------------------------
    // Add the file, checking for and correcting name collisions.
    //
    // The user's root list is locked as this file is added.
    try {
      $ary = self::addFilesInternal(
        NULL,
        [$archiveFile],
        (-1),
        TRUE,
        TRUE,
        FALSE);
    }
    catch (\Exception $e) {
      // On any failure, the archive wasn't added and we need to delete it.
      $archiveFile->delete();
      throw $e;
    }
 
    if (empty($ary) === TRUE) {
      return NULL;
    }
 
    return $ary[0];
  }
 
  /**
   * {@inheritdoc}
   */
  public function archiveToFolder(array $items) {
    //
    // Validate.
    // ---------
    // ZIP extensions must be supported and all items must be children
    // of this folder.
    if (empty($items) === TRUE) {
      return NULL;
    }
 
    if (ManageFilenameExtensions::isZipExtensionAllowed() === FALSE) {
      throw new ValidationException(FormatUtilities::createFormattedMessage(
        t(
          '@method was called to create an archive type the site does not support.',
          [
            '@method' => __METHOD__,
          ]),
        t('The ZIP file type required for new archives is not an allowed file type for the site. Archive creation is therefore not supported.')));
    }
 
    if ($this->isFolder() === FALSE) {
      throw new ValidationException(FormatUtilities::createFormattedMessage(
        t(
          '@method was called with an item that is not a folder.',
          [
            '@method' => __METHOD__,
          ])));
    }
 
    $parentId = (int) $this->id();
    $itemsToArchive = [];
    foreach ($items as $item) {
      if ($item !== NULL) {
        if ($item->getParentFolderId() !== $parentId) {
          throw new ValidationException(FormatUtilities::createFormattedMessage(
            t(
              '@method was called with an item that is not a folder.',
              [
                '@method' => __METHOD__,
              ])));
        }
        $itemsToArchive[] = $item;
      }
    }
 
    if (empty($itemsToArchive) === TRUE) {
      return NULL;
    }
 
    //
    // Create archive in local temp storage.
    // -------------------------------------
    // Create a ZIP archive containing the given items.
    //
    // On completion, a ZIP archive exists on the local file system in a
    // temporary file. This throws an exception and deletes the archive on
    // an error.
    $archiveUri = self::createZipArchive($itemsToArchive);
 
    //
    // Create File entity.
    // -------------------
    // Create a new File entity from the local file. This also moves the
    // file into FolderShare's directory tree and gives it a numeric name.
    // The MIME type is also set, and the File is marked as permanent.
    // This throws an exception if the File could not be created.
    try {
      if (count($itemsToArchive) === 1) {
        // Add '.zip' to the item name.
        $archiveName = $itemsToArchive[0]->getName() . '.zip';
      }
      else {
        // Use a generic name.
        $archiveName = Settings::getNewZipArchiveName();
      }
 
      $archiveFile = self::createFileEntityFromLocalFile(
        $archiveUri,
        $archiveName);
    }
    catch (\Exception $e) {
      FileUtilities::unlink($archiveUri);
      throw $e;
    }
 
    //
    // Add the archive to this folder.
    // -------------------------------
    // Add the file, checking for and correcting name collisions.
    //
    // The root folder tree is locked as this file is added.
    try {
      $ary = self::addFilesInternal(
        $this,
        [$archiveFile],
        (-1),
        TRUE,
        TRUE,
        FALSE);
    }
    catch (\Exception $e) {
      // On any failure, the archive wasn't added and we need to delete it.
      $archiveFile->delete();
      throw $e;
    }
 
    if (empty($ary) === TRUE) {
      return NULL;
    }
 
    return $ary[0];
  }
 
  /*---------------------------------------------------------------------
   *
   * Archive to file.
   *
   *---------------------------------------------------------------------*/
 
  /**
   * Creates and adds a list of children to a local ZIP archive.
   *
   * A new ZIP archive is created in the site's temporary directory
   * on the local file system. The given list of children are then
   * added to the archive and the file path of the archive is returned.
   *
   * If an error occurs, an exception is thrown and the archive file is
   * deleted.
   *
   * If a URI for the new archive is not provided, a temporary file is
   * created in the site's temporary directory, which is normally cleaned
   * out regularly. This limits the lifetime of the file, though
   * callers should delete the file when it is no longer needed, or move
   * it out of the temporary directory.
   *
   * <B>Process locks</B>
   * Each child file or folder and all of their subfolders and files are
   * locked for exclusive editing access by this function for the duration of
   * the archiving.
   *
   * @param \Drupal\foldershare\FolderShareInterface[] $items
   *   An array of FolderShare files and/or folders that are to be included
   *   in a new ZIP archive. They should all be children of the same parent
   *   folder.
   * @param string $archiveUri
   *   (optional, default = '' = create temp name) The URI for a local file
   *   to be overwritten with the new ZIP archive. If the URI is an empty
   *   string, a temporary file with a randomized name will be created in
   *   the site's temporary directory. The name will not have a filename
   *   extension.
   *
   * @return string
   *   Returns a URI to the new ZIP archive. The URI refers to a new file
   *   in the module's temporary files directory, which is cleaned out
   *   periodically. Callers should move the file to a new destination if
   *   they intend to keep the file.
   *
   * @throws \Drupal\foldershare\Entity\Exception\LockException
   *   Thrown if an access lock on any child could not be acquired.
   *
   * @throws \Drupal\foldershare\Entity\Exception\SystemException
   *   Thrown if the archive file could not be created, such as if the
   *   temporary directory does not exist or is not writable, if a temporary
   *   file for the archive could not be created, or if any of the file
   *   children of this item could not be read and saved into the archive.
   */
  public static function createZipArchive(
    array $items,
    string $archiveUri = '') {
 
    // Implementation note: How PHP's ZipArchive class works.
    //
    // Creating a ZipArchive selects a file name for the output.  Adding files
    // to the archive just adds those files to a list in memory of files
    // TO BE ADDED, but doesn't add them immediately or do any file I/O. The
    // actual file I/O occurs entirely when close() is called.
    //
    // This impacts when we lock files and folders. We cannot just lock
    // them before and after the archive addFile() call because that call
    // doesn't do the file I/O. Our locks would not guarantee that the file
    // continues to exist until the file I/O really happens on close().
    //
    // Instead, we need to lock everything FIRST, before adding anything to
    // the archive. Then we need to add the files and close the
    // archive to trigger the file I/O. And then when the file I/O is done
    // we can safely unlock everything LAST.
    //
    // Setup
    // -----
    // Sweep through the list of items to create a list of unique roots
    // above them.
    $rootIds = [];
    foreach ($items as $item) {
      if ($item === NULL) {
        // Item does not exist.
        continue;
      }
 
      $rootIds[] = $item->getRootItemId();
    }
 
    //
    // Lock roots.
    // -----------
    // Lock each of the root folder trees containing items to add to the
    // ZIP archive. Typically, all of the items are in the same folder
    // tree, so there will be just one root.
    //
    // Locking the root folder trees keeps them from changing out from
    // under this operation.
    $rootIds = array_unique($rootIds);
    foreach ($rootIds as $index => $rootId) {
      // LOCK ITEM ROOT FOLDER TREES.
      if (self::acquireRootOperationLock($rootId) === FALSE) {
        // Failed to get lock. Back out and unlock everything locked so far.
        //
        // UNLOCK ITEM ROOT FOLDER TREES.
        foreach ($rootIds as $i => $rid) {
          if ($i === $index) {
            break;
          }
          self::releaseRootOperationLock($rid);
        }
 
        throw new LockException(
          self::getStandardLockExceptionMessage(t('compressed'), NULL));
      }
    }
 
    //
    // Create ZIP file.
    // ----------------
    // Create a new empty ZIP file and URI, unless one was given.
    if (empty($archiveUri) === TRUE) {
      // Create an empty temporary file. The file will have a randomized
      // name guaranteed not to collide with anything.
      $archiveUri = FileUtilities::tempnam(
        ManageFileSystem::getTempDirectoryUri(),
        'zip');
 
      if ($archiveUri === FALSE) {
        // This can fail if file system permissions are messed up, the
        // file system is full, or some other system error has occurred.
        //
        // UNLOCK ITEM ROOT FOLDER TREES.
        foreach ($rootIds as $rootId) {
          self::releaseRootOperationLock($rootId);
        }
 
        throw new SystemException(t(
          "System error. A file at '@path' could not be created.\nThere may be a problem with directories or permissions. Please report this to the site administrator.",
          [
            '@path' => $archiveUri,
          ]));
      }
    }
 
    //
    // Add to ZIP.
    // -----------
    // Set up the ZIP archive, add a comment, then loop over all of the
    // items to add to the ZIP archive. Each time a folder is added,
    // recurse through that folder's descendants and add them too.
    // Finally, close the archive.
    $archive = NULL;
    $failException = NULL;
    try {
      //
      // Create the ZipArchive object.
      // -----------------------------
      // Create the archiver and assign it the output file.
      $archive = new \ZipArchive();
      $archivePath = FileUtilities::realpath($archiveUri);
      if ($archive->open($archivePath, \ZipArchive::OVERWRITE) !== TRUE) {
        // All errors that could be returned are very unlikely. The
        // archive's path is known to be good since we just created a
        // temp file using it.  This means permissions are right, the
        // directory and file name are fine, and the file system is up
        // and working. Something catestrophic now has happened.
        $failException = new SystemException(t(
          "System error. A file at '@path' could not be created.\nThere may be a problem with directories or permissions. Please report this to the site administrator.",
          [
            '@path' => $archivePath,
          ]));
      }
      else {
        $archive->setArchiveComment(Settings::getNewZipArchiveComment());
 
        //
        // Recursively add to archive.
        // ---------------------------
        // For each of the given items, add them to the archive.  An exception
        // is thrown if any child, or its children, cannot be added. That
        // causes us to abort.
        foreach ($items as $item) {
          if ($item !== NULL) {
            $item->addToZipArchiveInternal($archive, '');
          }
        }
 
        // Close the file and trigger I/O to build the archive.
        if ($archive->close() === FALSE) {
          // Something went wrong with the file I/O. The ZIP
          // library delays all of its I/O until the close, so we actually
          // don't know which specific operation failed.
          $failException = new SystemException(t(
            "System error. A file at '@path' could not be written.\nThere may be a problem with permissions. Please report this to the site administrator.",
            [
              '@path' => $archivePath,
            ]));
        }
      }
    }
    catch (\Exception $e) {
      $failException = $e;
    }
 
    //
    // Unlock roots.
    // -------------
    // All of the items have been added to the ZIP archive now. Unlock
    // all of the root folder trees above them.
    //
    // UNLOCK ITEM ROOT FOLDER TREES.
    foreach ($rootIds as $rootId) {
      self::releaseRootOperationLock($rootId);
    }
 
    if ($failException !== NULL) {
      // On failure, clean out the archive, delete the output file,
      // unlock everyting.
      if ($archive !== NULL) {
        $archive->unchangeAll();
        unset($archive);
      }
 
      // Delete the archive file as it exists so far.
      FileUtilities::unlink($archiveUri);
 
      throw $failException;
    }
 
    // Change the permissions to be suitable for web serving.
    FileUtilities::chmod($archiveUri);
 
    return $archiveUri;
  }
 
  /*---------------------------------------------------------------------
   *
   * Archive implementation.
   *
   *---------------------------------------------------------------------*/
 
  /**
   * Adds this item to the archive and recurses.
   *
   * This item is added to the archive, and then all its children are
   * added.
   *
   * If this item is a folder, all of the folder's children are added to
   * the archive. If the folder is empty, an empty directory is added to
   * the archive.
   *
   * If this item is a file, the file on the file system is copied into
   * the archive.
   *
   * To insure that the archive has the user-visible file and folder names,
   * an archive path is created during recursion. On the first call, a base
   * path is passed in as $baseZipPath. This item's name is then appended.
   * If this item is a file, the path with appended name is used as the
   * name for the file when in the archive. If this item is a folder, this
   * path with appended name is passed as the base path for adding the
   * folder's children, and so on.
   *
   * <B>Process locks</B>
   * This method does not lock access. The caller should lock around changes
   * to the entity.
   *
   * @param \ZipArchive $archive
   *   The archive to add to.
   * @param string $baseZipPath
   *   The folder path to be used within the ZIP archive to lead to the
   *   parent of this item.
   *
   * @throws \Drupal\foldershare\Entity\Exception\LockException
   *   Thrown if an access lock on this folder could not be acquired.
   *   This exception is never thrown if $lock is FALSE.
   *
   * @throws \Drupal\foldershare\Entity\Exception\SystemException
   *   Thrown if a file could not be added to the archive.
   */
  private function addToZipArchiveInternal(
    \ZipArchive &$archive,
    string $baseZipPath) {
    //
    // Implementation note:
    //
    // The file path used within a ZIP file is recommended to always use
    // the '/' directory separator, regardless of the local OS conventions.
    // Since we are building a ZIP path, we therefore use '/'.
    //
    // Use the incoming folder path and append the user-visible item name
    // to create the name for the item as it should appear in the ZIP archive.
    if (empty($baseZipPath) === TRUE) {
      $currentZipPath = $this->getName();
    }
    else {
      $currentZipPath = $baseZipPath . '/' . $this->getName();
    }
 
    // Add the item to the archive.
    switch ($this->getKind()) {
      case self::FOLDER_KIND:
        // For folders, create an empty directory entry in the ZIP archive.
        // Then recurse to add all of the folder's children.
        if ($archive->addEmptyDir($currentZipPath) === FALSE) {
          throw new SystemException(t(
            "System error. Cannot add '@path' to archive '@archive'.\nThere may be a problem with the file system (such as out of storage space), with file permissions, or with the ZIP archive library. Please report this to the site administrator.",
            [
              '@path'    => 'empty directory',
              '@archive' => $currentZipPath,
            ]));
        }
 
        foreach ($this->findChildren() as $child) {
          $child->addToZipArchiveInternal($archive, $currentZipPath);
        }
        break;
 
      case self::FILE_KIND:
        // For files, get the path to the underlying stored file and add
        // the file to the archive.
        $file     = $this->getFile();
        $fileUri  = $file->getFileUri();
        $filePath = FileUtilities::realpath($fileUri);
 
        if ($archive->addFile($filePath, $currentZipPath) === FALSE) {
          throw new SystemException(t(
            "System error. Cannot add '@path' to archive '@archive'.\nThere may be a problem with the file system (such as out of storage space), with file permissions, or with the ZIP archive library. Please report this to the site administrator.",
            [
              '@path'    => $filePath,
              '@archive' => $currentZipPath,
            ]));
        }
        break;
 
      case self::IMAGE_KIND:
        // For images, get the path to the underlying stored file and add
        // the file to the archive.
        $file     = $this->getImage();
        $fileUri  = $file->getFileUri();
        $filePath = FileUtilities::realpath($fileUri);
 
        if ($archive->addFile($filePath, $currentZipPath) === FALSE) {
          throw new SystemException(t(
            "System error. Cannot add '@path' to archive '@archive'.\nThere may be a problem with the file system (such as out of storage space), with file permissions, or with the ZIP archive library. Please report this to the site administrator.",
            [
              '@path'    => $filePath,
              '@archive' => $currentZipPath,
            ]));
        }
        break;
 
      case self::MEDIA_KIND:
      default:
        // For any other kind, we don't know what it is or it does not have
        // a stored file, so we cannot add it to the archive. Silently
        // ignore it.
        break;
    }
  }
 
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc