azure_blob_fs-8.x-1.0-beta3/src/StreamWrapper/AzureBlobFsStream.php

src/StreamWrapper/AzureBlobFsStream.php
<?php

namespace Drupal\azure_blob_fs\StreamWrapper;

use ArrayObject;
use Drupal\azure_blob_fs\Constants\Services as ModuleServices;
use Drupal\Core\StreamWrapper\PublicStream;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\Core\StreamWrapper\StreamWrapperManager;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use MicrosoftAzure\Storage\Blob\BlobRestProxy;
use MicrosoftAzure\Storage\Blob\Models\Blob;
use MicrosoftAzure\Storage\Blob\Models\BlobPrefix;
use MicrosoftAzure\Storage\Blob\Models\Block;
use MicrosoftAzure\Storage\Blob\Models\BlockList;
use MicrosoftAzure\Storage\Blob\Models\GetBlobResult;
use MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions;
use MicrosoftAzure\Storage\Common\Exceptions\ServiceException;

/**
 * Defines a Drupal Azure Blob stream wrapper class.
 *
 * Provides support for storing files on a Microsoft Azure Blob Storage for the
 * Drupal file interface.
 *
 * Now I'll be honest, there's still a lot of things to fix and understand in
 * this file, but this is THE file to modify when it comes to the core functions
 * of the module.
 */
abstract class AzureBlobFsStream implements StreamWrapperInterface {
    // String Translation Trait.
    use StringTranslationTrait;

    /**
     * The Azure Blob Storage client.
     *
     * @var \MicrosoftAzure\Storage\Blob\BlobRestProxy
     */
    protected static $client;

    /**
     * The Azure Blob Storage Account Name.
     *
     * @var string
     */
    protected static $accountName;

    /**
     * The Azure Blob Storage Account Key.
     *
     * @var string
     */
    protected static $accountKey;

    /**
     * The Azure Blob Storage URL.
     *
     * @var string
     */
    protected static $blobStorageUrl;

    /**
     * The Azure Blob Storage SAS Token.
     *
     * @var string
     */
    protected static $blobStorageSASToken;

    /**
     * The Azure Blob Storage blob container being used.
     *
     * @var string
     */
    protected static $container;

    /**
     * Store the local stream base URL.
     *
     * @var string
     */
    protected static $localStreamBaseUrl;

    /**
     * The Azure Blob FS service injected through DI.
     *
     * @var \Drupal\azure_blob_fs\Service\AzureBlobFsServiceInterface
     */
    protected $azureBlobFsService;

    /**
     * What the fuck is this?
     *
     * @var \ArrayIterator
     */
    protected $iterator;

    /**
     * Mode in which the stream was opened.
     *
     * @var string
     */
    protected $mode;

    /**
     * Instance uri referenced as "<scheme>://key".
     *
     * @var string
     */
    protected $uri;

    /**
     * A generic resource handle.
     *
     * @var resource
     */
    public $handle;

    /**
     * Constructs a new AzureBlobFsStream object.
     */
    public function __construct() {
        $this->azureBlobFsService = \Drupal::service(ModuleServices::AZURE_BLOB_FS);
        // Set static settings from the site.
        static::$accountName =  $this->azureBlobFsService->getConfiguredAccountName();
        static::$accountKey =  $this->azureBlobFsService->getConfiguredAccountKey();
        static::$blobStorageUrl = $this->azureBlobFsService->getConfiguredUrl();
        static::$blobStorageSASToken = $this->getConfiguredSasToken();
        static::$container = $this->getConfiguredContainerName();
        static::$localStreamBaseUrl = PublicStream::baseUrl();
    }

    /**
     * Return the scheme used in this stream.
     *
     * For this module, it's either 'public' or 'private'.
     *
     * @return string
     *   The scheme being used in this stream.
     */
    abstract protected function scheme(): string;

    /**
     * Return the configured sas token depending on the Stream.
     *
     * There may be a sas token per stream.
     *
     * @return string
     *   The configured sas token.
     */
    protected function getConfiguredSasToken(): string {
        return $this->azureBlobFsService->getConfiguredSasToken($this->scheme());
    }

    /**
     * Return the configured container name depending on the Stream.
     *
     * There is a container per stream.
     *
     * @return string
     *   The configured container name.
     */
    protected function getConfiguredContainerName(): string {
        return $this->azureBlobFsService->getConfiguredContainerName($this->scheme());
    }


    /**
     * {@inheritdoc}
     */
    public static function getType(): int {
        return StreamWrapperInterface::NORMAL;
    }

    /**
     * {@inheritdoc}
     */
    public function getName(): string {
        return $this->t('Azure Blob File System Stream');
    }

    /**
     * {@inheritdoc}
     */
    public function getDescription(): string {
        return $this->t('Provides an Azure Blob File Storage service-based remote file system to Drupal');
    }

    /**
     * Gets the path that the wrapper is responsible for.
     *
     * This function isn't part of DrupalStreamWrapperInterface, but the rest
     * of Drupal calls it as if it were, so we need to define it.
     *
     * {@inheritdoc}
     */
    public function getDirectoryPath(): string {
        return '';
    }

    /**
     * {@inheritdoc}
     */
    public function setUri($uri): void {
        $this->uri = $uri;
    }

    /**
     * {@inheritdoc}
     */
    public function getUri(): string {
        return $this->uri;
    }

    /**
     * Get external URL of a resource.
     *
     * We also handle image style derivatives in here.
     *
     * It may not be ideal to do this here for the sake of performance. In the
     * future, we need to find a way to refactor our code and handle image styles
     * better.
     *
     * The Amazon S3 File System module handles image styles more like how the
     * core does it, but many problems arose when I first tried to implement that.
     *
     * This is the solution for now.
     *
     * {@inheritdoc}
     */
    public function getExternalUrl(): string {
        // Get the target destination without the scheme.
        $target = StreamWrapperManager::getTarget($this->uri);

        // If this is an image style request, we want to fallback to our local fs.
        if (strpos($target, 'styles/') === 0) {
            return static::$localStreamBaseUrl . '/' . $target;
        }

        // If there is no target we won't return path to the bucket,
        // instead we'll return empty string.
        if (empty($target)) {
            return '';
        }

        // Get the remote URI.
        $remoteUri = $this->getRemoteFilePath($target) ?? '';

        // If the remote URI is empty, we can stop here.
        return $remoteUri;
    }

    /**
     * This wrapper does not support realpath().
     *
     * {@inheritdoc}
     */
    public function realpath(): bool {
        return FALSE;
    }

    /**
     * Emulates the closing of a directory handle.
     *
     * Sets our stream's iterator to NULL.
     *
     * {@inheritdoc}
     */
    public function dir_closedir(): bool {
        $this->iterator = NULL;
        return TRUE;
    }

    /**
     * Emulates the opening of a directory handle.
     *
     * Sets up our iterator with the given uri if it's a directory.
     *
     * {@inheritdoc}
     */
    public function dir_opendir($uri, $options): bool {
        // Get the path.
        $path = StreamWrapperManager::getTarget($uri);

        // Get remote path info.
        $remotePathInfo = $this->remotePathInfo($path);

        // If this path is a file, we return TRUE.
        if (isset($remotePathInfo[1]) && $remotePathInfo[1] === 'file') {
            // Path is a file path. Returns TRUE without creating the iterator.
            return FALSE;
        }

        // Get the blobs from the virtual directory.
        $blobs = $this->getVirtualFolder($path, ['exclude_dirs' => TRUE]);
        if ($blobs === NULL) {
            return FALSE;
        }

        // Build our iterator.
        $blobsArrayObj = new ArrayObject($blobs);
        $this->iterator = $blobsArrayObj->getIterator();

        return TRUE;
    }

    /**
     * {@inheritdoc}
     */
    public function dir_readdir() {
        // If our iterator is empty, there is nothing to read.
        // This also means there is likely no directory.
        if ($this->iterator === NULL) {
            return FALSE;
        }

        // Get the current iterator item.
        $current = $this->iterator->current();

        // If there is no current item, we are at the end of our iterator.
        if ($current === NULL) {
            return FALSE;
        }

        // Proceed to the next item.
        $this->iterator->next();

        // Return the current item's name, or FALSE otherwise.
        return basename($current->getName()) ?? FALSE;
    }

    /**
     * {@inheritdoc}
     */
    public function dir_rewinddir(): ?bool {
        // If our iterator is empty, there is nothing to rewind.
        // This also means there is likely no directory.
        if ($this->iterator === NULL) {
            return FALSE;
        }

        // Try to rewind our iterator.
        try {
            $this->iterator->rewind();
            return TRUE;
        }
        catch (ServiceException $e) {
            watchdog_exception('Azure Blob Stream Service Exception (dir_rewinddir)', $e);
            return FALSE;
        }
        catch (\Exception $e) {
            watchdog_exception('Azure Blob Stream Exception (dir_rewinddir)', $e);
            return FALSE;
        }
    }

    /**
     * Azure Blob Storages don't support physical directories so always return
     * TRUE.
     *
     * There is the concept of Virtual Folders, but they can't be created
     * programmatically. So we'll just ignore that.
     *
     * {@inheritdoc}
     */
    public function mkdir($path, $mode, $options): bool {
        return TRUE;
    }

    /**
     * Called in response to rename() to rename a file or directory. Currently
     * only supports renaming objects.
     *
     * In Azure Blob Storage, we have to copy the existing blob to a new one with
     * the new name, then delete the original.
     *
     * Now...Renaming folders might be very heavy. We'll see about adding that in
     * the future.
     *
     * {@inheritdoc}
     */
    public function rename($path_from, $path_to): bool {
        try {
            $this->getClient()->copyBlob(static::$container, $path_to, static::$container, $path_from);
            $this->getClient()->deleteBlob(static::$container, $path_from);
            clearstatcache(TRUE, $path_from);
            clearstatcache(TRUE, $path_to);
        }
        catch (ServiceException $e) {
            watchdog_exception('Azure Blob Stream Service Exception (rename)', $e);
            return FALSE;
        }
        catch (\Exception $e) {
            watchdog_exception('Azure Blob Stream Exception (rename)', $e);
            return FALSE;
        }
        return TRUE;
    }

    /**
     * {@inheritdoc}
     */
    public function rmdir($uri, $options): bool {
        return $this->deleteRemotePath($uri);
    }

    /**
     * {@inheritdoc}
     */
    public function stream_cast($cast_as) {
        return $this->handle ?: FALSE;
    }

    /**
     * {@inheritdoc}
     */
    public function stream_close(): bool {
        return fclose($this->handle);
    }

    /**
     * {@inheritdoc}
     */
    public function stream_eof(): bool {
        return feof($this->handle);
    }

    /**
     * The actual upload to Azure is handled in this function.
     *
     * @see https://www.php.net/manual/en/function.fflush.php
     *
     * {@inheritdoc}
     */
    public function stream_flush(): ?bool {
        if ($this->mode === 'r') {
            return FALSE;
        }

        try {
            // Seek to 0.
            fseek($this->handle, 0);

            $blockId = substr(base64_encode(basename($this->uri)), 0, 64);
            $blocks = [new Block($blockId, 'Uncommitted')];
            $blockList = BlockList::create($blocks);

            $blobName = StreamWrapperManager::getTarget($this->uri);

            $this->getClient()->createBlobBlock(static::$container, $blobName, $blockId, $this->handle);
            $this->getClient()->commitBlobBlocks(static::$container, $blobName, $blockList);

            $this->iterator = FALSE;

            return TRUE;
        }
        catch (ServiceException $e) {
            watchdog_exception('Azure Blob Stream Service Exception (stream_flush)', $e);
            \Drupal::messenger()->addWarning('The file "'.$this->uri.'" could not be created in the Azure Blob Storage. Please review the recent log messages for additional details.');
            return FALSE;
        }
        catch (\Exception $e) {
            watchdog_exception('Azure Blob Stream Exception (stream_flush)', $e);
            \Drupal::messenger()->addWarning('The file "'.$this->uri.'" could not be created in the Azure Blob Storage. Please review the recent log messages for additional details.');
            return FALSE;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function stream_lock($operation): bool {
        if (\in_array($operation, [LOCK_SH, LOCK_EX, LOCK_UN, LOCK_NB], TRUE)) {
            return flock($this->handle, $operation);
        }

        return TRUE;
    }

    /**
     * Sets metadata on the stream.
     *
     * Manual recommends return FALSE for not implemented options, but Drupal
     * requires TRUE in some cases like chmod for avoid watchdog errors.
     *
     * @see http://php.net/manual/en/streamwrapper.stream-metadata.php
     * @see \Drupal\Core\File\FileSystem::chmod()
     *
     * Returns FALSE if the option is not included in bypassed_options array
     * otherwise, TRUE.
     *
     * @see http://php.net/manual/streamwrapper.stream-metadata.php
     *
     * {@inheritdoc}
     */
    public function stream_metadata($path, $option, $value): bool {
        $bypassed_options = [STREAM_META_ACCESS];
        return \in_array($option, $bypassed_options, TRUE);
    }

    /**
     * When opening a file, we want to get it from the Azure Blob.
     *
     * {@inheritdoc}
     */
    public function stream_open($uri, $mode, $options, &$opened_path): bool {
        $this->setUri($uri);
        $this->mode = rtrim($mode, 'bt+');

        switch ($this->mode) {
            case 'r': return $this->openReadStream();
            case 'a': return $this->openAppendStream();
            default: return $this->openWriteStream();
        }
    }

    /**
     * Open a file reading stream.
     *
     * This is inspired by the Amazon S3 module. Subject to change.
     *
     * @return bool
     *   Returns TRUE on success, FALSE on failure.
     */
    private function openReadStream(): bool {
        // Get the target destination without the scheme.
        $target = StreamWrapperManager::getTarget($this->uri);

        // We try to get a blob.
        // Get the blob.
        $blob = $this->getBlob($target);

        // If the blob is empty, we get outta here.
        if ($blob !== NULL) {
            // Get the contents and store them in our handle.
            $this->handle = $blob->getContentStream();
        } else {
            return FALSE;
        }

        return TRUE;
    }

    /**
     * Open a file writing stream.
     *
     * This is inspired by the Amazon S3 module. Subject to change.
     *
     * @return bool
     *   Returns TRUE regardless.
     */
    private function openWriteStream(): bool {
        $this->handle = fopen('php://temp', 'r+b');
        return TRUE;
    }

    /**
     * Open a file appending stream.
     *
     * This is inspired by the Amazon S3 module. Subject to change.
     *
     * @return bool|null
     *   Returns TRUE upon completion.
     */
    private function openAppendStream(): ?bool {
        // Get the target destination without the scheme.
        $target = StreamWrapperManager::getTarget($this->uri);

        // Get the blob.
        $blob = $this->getBlob($target);

        // If the blob is empty, we get outta here.
        if ($blob !== NULL) {
            // Set the the handle
            $this->handle = $blob->getContentStream();
            @fseek($this->handle, 0, SEEK_END);
            return TRUE;
        }

        // The object does not exist, so use a simple write stream
        return $this->openWriteStream();
    }

    /**
     * {@inheritdoc}
     */
    public function stream_read($count): string {
        return fread($this->handle, $count);
    }

    /**
     * {@inheritdoc}
     */
    public function stream_seek($offset, $whence = SEEK_SET): bool {
        return !fseek($this->handle, $offset, $whence);
    }

    /**
     * {@inheritdoc}
     */
    public function stream_set_option($option, $arg1, $arg2): bool {
        return FALSE;
    }

    /**
     * @noinspection ReturnTypeCanBeDeclaredInspection
     *
     * {@inheritdoc}
     */
    public function stream_stat() {
        return fstat($this->handle);
    }

    /**
     * {@inheritdoc}
     */
    public function stream_tell(): int {
        return ftell($this->handle);
    }

    /**
     * {@inheritdoc}
     */
    public function stream_truncate($new_size): bool {
        return ftruncate($this->handle, $new_size);
    }

    /**
     * {@inheritdoc}
     */
    public function stream_write($data): int {
        return fwrite($this->handle, $data);
    }

    /**
     * {@inheritdoc}
     */
    public function unlink($uri): bool {
        // Delete what is found at the provided URI.
        return $this->deleteRemotePath($uri);
    }

    /**
     * @TODO - This needs to be revised...
     *  @noinspection ReturnTypeCanBeDeclaredInspection
     *
     * {@inheritdoc}
     */
    public function url_stat($uri, $flags) {
        // Get target.
        $path = StreamWrapperManager::getTarget($uri);

        // Attempt to get information on the path.
        $pathInfo = $this->remotePathInfo($path);

        // Initialize the stat array.
        $stat = array_fill_keys([
            'dev',
            'ino',
            'mode',
            'nlink',
            'uid',
            'gid',
            'rdev',
            'size',
            'atime',
            'mtime',
            'ctime',
            'blksize',
            'blocks'
        ], 0);

        // If our path information is false, we return here.
        if (!$pathInfo[0]) {
            return FALSE;
        }

        // If we have ourselves a file, get file statistics.
        if ($pathInfo[1] === 'file') {
            $blob = $pathInfo[2];
            /* @var \MicrosoftAzure\Storage\Blob\Models\BlobProperties $properties */
            $properties = $blob->getProperties();
            // All files are considered writable, so OR in 0777.
            $stat['mode'] = 0100000 | 0777;
            $stat['size'] = $properties->getContentLength();
            $stat['mtime'] = date_timestamp_get($properties->getLastModified());
            $stat['blksize'] = -1;
            $stat['blocks'] = -1;
            return $stat;
        }

        // If we have ourselves a directory, then I have no fucking idea what to do.
        if ($pathInfo[1] === 'folder') {
            // Do things.
            return FALSE;
        }

        return FALSE;
    }

    /**
     * {@inheritdoc}
     */
    public function dirname($uri = NULL): string {
        if ($uri === NULL) {
            $uri = $this->uri;
        }

        $fs = \Drupal::service('file_system');
        $scheme = StreamWrapperManager::getScheme($uri);
        $dirname = $fs->dirname(StreamWrapperManager::getTarget($uri));

        // When the dirname() call above is given '$scheme://', it returns '.'.
        // But '$scheme://.' is an invalid uri, so we return "$scheme://" instead.
        if ($dirname === '.') {
            $dirname = '';
        }

        return "$scheme://$dirname";
    }

    /**
     * Return a client connection to an Azure Blob Storage.
     *
     * @return \MicrosoftAzure\Storage\Blob\BlobRestProxy
     *   The generated Azure Blob client.
     */
    protected function getClient(): BlobRestProxy {
        // Return our static variable if it's set.
        if (static::$client !== NULL) {
            return static::$client;
        }

        // If the client hasn't been set up yet, or the config given to this call is
        // different from the previous call, (re)build the client.
        $connectionString = $this->buildConnectionString(static::$blobStorageUrl, static::$blobStorageSASToken);

        // Create the client.
        static::$client = BlobRestProxy::createBlobService($connectionString);
        return static::$client;
    }

    /**
     * Build an Azure File Storage connection string.
     *
     * @param string $url
     *   The Blob Storage URL.
     * @param string $sas
     *   The SAS token.
     *
     * @return string
     *   The connection string if it was properly built.
     */
    protected function buildConnectionString(string $url, string $sas): string {
        return 'BlobEndpoint=' . $url . ';SharedAccessSignature=' . $sas;
    }

    /**
     * Get a remote file from the Azure Blob Storage.
     *
     * @param string $path
     *   Path to the remote file.
     *
     * @return null|resource
     *   Returns the resource of the file if found, NULL otherwise.
     */
    protected function getRemoteFile(string $path) {
        // @TODO - Document your shit!
        // Attempt to get file from path.
        $blob = $this->getBlob($path);

        if ($blob === NULL)  {
            return NULL;
        }

        return $blob->getContentStream();
    }

    /**
     * Get the real path to a remote file given a relative path.
     *
     * @param string $path
     *   Path to the file relative to the Blob Storage.
     *
     * @return null|string
     *   The full remote path to the file upon success, NULL otherwise.
     */
    protected function getRemoteFilePath(string $path): ?string {
        // @TODO - Document your shit!
        // Attempt to get file from path.
        try {
          return $this->getClient()->getBlobUrl(static::$container, $path);
        }
        catch (ServiceException $e) {
            watchdog_exception('Azure Blob Stream Service Exception (getRemoteFilePath)', $e);
            return NULL;
        }
        catch (\Exception $e) {
            watchdog_exception('Azure Blob Stream Exception (getRemoteFilePath)', $e);
            return NULL;
        }
    }

    /**
     * Get Azure Blob from the remote storage.
     *
     * @param string $path
     *   The path, or "name" of the blob.
     *
     * @return \MicrosoftAzure\Storage\Blob\Models\GetBlobResult|null
     *  Returns a blob if it was found, or NULL otherwise.
     */
    protected function getBlob(string $path): ?GetBlobResult {
        // Attempt to get file from path.
        try {
            // If it was found, return it straight up.
            return $this->getClient()->getBlob(static::$container, $path);
        }
        catch (ServiceException $e) {
            if ($e->getCode() !== 404) {
                watchdog_exception('Azure Blob Stream Service Exception (getBlob)', $e);
            }
            return NULL;
        }
        catch (\Exception $e) {
            watchdog_exception('Azure Blob Stream Exception (getBlob)', $e);
            return NULL;
        }
    }

    /**
     * Get Azure Virtual Folder from the remote storage.
     *
     * Azure doesn't have REAL directories or folders. They're virtual. Only the
     * full path of each blob is saved.
     *
     * @param string $path
     *   The path to the virtual folder.
     * @param array $options
     *   Custom options to affect the returned output.
     *
     * @return array|\MicrosoftAzure\Storage\Blob\Models\Blob[]|null
     *   Return an array of Blobs/BlobPrefixes, or NULL if nothing was found.
     */
    protected function getVirtualFolder(string $path, array $options = ['exclude_dirs' => FALSE]) : ?array {
        // Set our prefix to the provided path.
        // The prefix will allow the following code to search for relevant blobs.
        // If the prefix is just a slash, we set the prefix to empty.
        // This will make us list all blobs at the root.
        $prefix = $path;
        if ($path === '/') {
            $prefix = '';
        }
        // Otherwise, if our prefix doesn't end with a slash, append one.
        elseif (substr($path, -1) !== '/') {
            $prefix = $path . '/';
        }

        // Set up our list options.
        $listOptions = new ListBlobsOptions();
        $listOptions->setPrefix($prefix);
        $listOptions->setDelimiter('/');
        try {
            $result = $this->getClient()->listBlobs(static::$container, $listOptions);
            $folderData = $result->getBlobs() ?? [];
            if (!$options['exclude_dirs']) {
                $blobPrefixes = $result->getBlobPrefixes() ?? [];
                // Merge in any blob prefixes as well.
                $folderData = array_merge($folderData, $blobPrefixes);
            }
        }
        catch (ServiceException $e) {
            watchdog_exception('Azure Blob Stream Service Exception (getVirtualFolder)', $e);
            $folderData = [];
        }
        catch (\Exception $e) {
            watchdog_exception('Azure Blob Stream Exception (getVirtualFolder)', $e);
            $folderData = [];
        }

        // If there are blobs, then we have ourselves a folder.
        if (!empty($folderData)) {
            return $folderData;
        }

        // Otherwise, return an empty array.
        return NULL;
    }

    /**
     * Get information on a remote path towards the Azure Blob Storage.
     *
     * This information will tell you if the path is void, is a blob block or is
     * a virtual folder.
     *
     * @param string $path
     *   The path to get information on.
     *
     * @return array
     *   An array of relevant information.
     */
    protected function remotePathInfo(string $path): array {
        // Returns information about whether a file exists or not.
        // First, if we find a blob, that means we have ourselves a file.
        $blob = $this->getBlob($path);
        if ($blob !== NULL) {
            return [TRUE, 'file', $blob];
        }

        // If the above doesn't go through, we check if it's a directory.
        $folder = $this->getVirtualFolder($path);
        if ($folder !== NULL) {
            return [TRUE, 'folder', $folder];
        }

        // Otherwise, return false, wrapped in a beautiful array.
        return [FALSE];
    }

    /**
     * Delete a blob of virtual folder at a given path.
     *
     * Use cautiously now!
     *
     * @param string $path
     *   The path that will be deleted.
     * @param array $options
     *   Options to affect the delete operation.
     *
     * @return bool
     *   Returns TRUE if the operation was successful, FALSE otherwise.
     */
    protected function deleteRemotePath(string $path, array $options = ['recursive' => FALSE]): bool {
        // Get the target.
        $path = StreamWrapperManager::getTarget($path);

        // First, we should check if the path is a file or directory.
        $pathInfo = $this->remotePathInfo($path);

        // If this remote path doesn't exist, return.
        if (!$pathInfo[0]) {
            return FALSE;
        }

        // Otherwise, we handle the case where this is a file.
        if ($pathInfo[1] === 'file') {
            $del = TRUE;
            try {
                $this->getClient()->deleteBlob(static::$container, $path);
            }
            catch (ServiceException|\Exception $e) {
                $del = FALSE;
            }

          return $del;
        }

        // Otherwise, we handle the case where this is a folder.
        if ($pathInfo[1] === 'folder') {
            $items = $pathInfo[2];

            // Only delete stuff if the
            if (!empty($items) && $options['recursive']) {
                /* @var \MicrosoftAzure\Storage\Blob\Models\Blob $blob */
                foreach ($items as $item) {
                    if ($item instanceof Blob) {
                        $blobName = $blob->getName();
                        $this->deleteRemotePath($blobName);
                    } elseif ($item instanceof BlobPrefix){
                        $this->deleteRemotePath($item->getName());
                    }
                }
            } else {
                return FALSE;
            }

        }

        return TRUE;
    }

    /**
     * Get the path to the configured Blob Storage.
     *
     * This takes the configurations straight from the site settings instead of
     * querying the blob itself. It saves some performance.
     *
     * @param bool $includeContainer
     *   Set this to TRUE to include the configured container in the path.
     *
     * @return string
     *   The URL to the Blob Storage.
     */
    protected function getStoragePath($includeContainer = false): string {
        // @TODO - Document your shit!
        return static::$blobStorageUrl . ($includeContainer ? '/' . static::$container : '');
    }

    /**
     * Extract container name from a given path.
     *
     * @param string $path
     *   External path to a file in the blob storage.
     *
     * @return string
     *   The container that this file is found in. Usually the first subdirectory
     *   level in a given URL towards the storage.
     */
    protected function extractContainerNameFromRemotePath($path): string {
        $url = parse_url($path);
        if ($url['host']) {
            return $url['host'];
        }
        return '';
    }

    /**
     * Extract file name from a given path.
     *
     * @param string $path
     *   The path to a file in the storage.
     *
     * @return string
     *   The file path stripped of the host and domain.
     */
    protected function extractFileNameFromRemotePath(string $path): string {
        $url = parse_url($path);
        if ($url['host']) {
            $fileName = $url['path'] ?? $url['host'];
            if (strpos($fileName, '/') === 0) {
                $fileName = substr($fileName, 1);
            }
            return $fileName;
        }
        return '';
    }
}

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

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