Skip to content

Commit 06b1de4

Browse files
committed
Initial commit
1 parent 9d29faf commit 06b1de4

12 files changed

+318
-0
lines changed

.editorconfig

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# For more information about the properties used in
2+
# this file, please see the EditorConfig documentation:
3+
# http://editorconfig.org/
4+
5+
[*]
6+
charset = utf-8
7+
end_of_line = lf
8+
indent_size = 4
9+
indent_style = space
10+
insert_final_newline = true
11+
trim_trailing_whitespace = true
12+
13+
[*.md]
14+
trim_trailing_whitespace = false
15+
16+
[*.{yml,js,json,css,scss,eslintrc,feature}]
17+
indent_size = 2
18+
indent_style = space
19+
20+
[composer.json]
21+
indent_size = 4

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.idea

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Silverstripe Azure Blob Storage
2+
3+
Silverstripe module to store assets in Azure Blob Storage rather than on the
4+
local filesystem. This leverages Flysystem in Silverstripe 4.
5+
6+
## Environment setup
7+
8+
The module requires a few environment variables to be set
9+
10+
* `AZURE_CONNECTION_URL`: The connection URL as from the dashboard
11+
* `AZURE_CONTAINER_NAME`: The name of the container to store assets in.
12+
13+
## Installation
14+
15+
```
16+
composer require fullscreeninteractive/silverstripe-azure-blob-storage
17+
```
18+
19+
**Note:** This currently immediately replaces the built-in local asset store that comes with
20+
SilverStripe with one based on Azure. Any files that had previously been uploaded to an existing
21+
asset store will be unavailable (though they won't be lost - just run `composer remove
22+
fullscreeninteractive/silverstripe-azure-blob-storage` to remove the module and restore access).
23+
24+
## Configuration
25+
26+
Assets are classed as either 'public' or 'protected' by SilverStripe. Public assets can be
27+
freely downloaded, whereas protected assets (e.g. assets not yet published) shouldn't be
28+
directly accessed.
29+
30+
The module supports this by streaming the contents of protected files down to the browser
31+
via the web server (as opposed to linking directly) by default. To ensure that
32+
protected assets can't be accessed, ensure you setup an appropriate policy.

_config/assetadmin.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
Name: silverstripe-azure-assetadmin
3+
Only:
4+
moduleexists: silverstripe/asset-admin
5+
---
6+
# Ensure we use links and not inline thumbnails for all assets
7+
SilverStripe\AssetAdmin\Model\ThumbnailGenerator:
8+
thumbnail_links:
9+
protected: url
10+
public: url

_config/assets.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
Name: silverstripe-azure-flysystem
3+
Only:
4+
envvarset: AZURE_CONNECTION_URL
5+
After:
6+
- '#assetsflysystem'
7+
---
8+
SilverStripe\Core\Injector\Injector:
9+
League\Flysystem\Adapter\Local:
10+
class: League\Flysystem\Adapter\Local
11+
constructor:
12+
root: '`TEMP_PATH`'
13+
FullscreenInteractive\SilverStripe\AzureStorage\Adapter\PublicAdapter:
14+
constructor:
15+
connectionUrl: '`AZURE_CONNECTION_URL`'
16+
containerName: '`AZURE_CONTAINER_NAME`'
17+
League\Flysystem\Cached\Storage\Memory.public:
18+
class: League\Flysystem\Cached\Storage\Memory
19+
League\Flysystem\Cached\Storage\Adapter.public:
20+
class: League\Flysystem\Cached\Storage\Adapter
21+
constructor:
22+
adapter: '%$League\Flysystem\Adapter\Local'
23+
file: 'azuremetadata/public'
24+
expire: 259200
25+
SilverStripe\Assets\Flysystem\PublicAdapter:
26+
class: FullscreenInteractive\SilverStripe\AzureStorage\Adapter\PublicCachedAdapter
27+
constructor:
28+
adapter: '%$FullscreenInteractive\SilverStripe\AzureStorage\Adapter\PublicAdapter'
29+
cache: '%$League\Flysystem\Cached\Storage\Adapter.public'
30+
FullscreenInteractive\SilverStripe\AzureStorage\Adapter\ProtectedAdapter:
31+
constructor:
32+
connectionUrl: '`AZURE_CONNECTION_URL`'
33+
containerName: '`AZURE_CONTAINER_NAME`'
34+
League\Flysystem\Cached\Storage\Adapter.protected:
35+
class: League\Flysystem\Cached\Storage\Adapter
36+
constructor:
37+
adapter: '%$League\Flysystem\Adapter\Local'
38+
file: 'azuremetadata/protected'
39+
expire: 259200
40+
SilverStripe\Assets\Flysystem\ProtectedAdapter:
41+
class: FullscreenInteractive\SilverStripe\AzureStorage\Adapter\ProtectedCachedAdapter
42+
constructor:
43+
adapter: '%$FullscreenInteractive\SilverStripe\AzureStorage\Adapter\ProtectedAdapter'
44+
cache: '%$League\Flysystem\Cached\Storage\Adapter.protected'
45+
---
46+
Name: silverstripe-azure-assetscore
47+
Only:
48+
envvarset: AZURE_CONNECTION_URL
49+
After:
50+
- '#assetscore'
51+
---
52+
SilverStripe\Core\Injector\Injector:
53+
SilverStripe\Assets\Storage\AssetStore:
54+
class: SilverStripe\Assets\Flysystem\FlysystemAssetStore

_config/tinymce.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
After:
3+
- '#corehtml'
4+
---
5+
SilverStripe\Core\Injector\Injector:
6+
League\Flysystem\Filesystem.localpublic:
7+
class: 'League\Flysystem\Filesystem'
8+
constructor:
9+
FilesystemAdapter: '%$FullscreenInteractive\SilverStripe\AzureStorage\Adapter\TinyMceAdapter'
10+
FilesystemConfig:
11+
visibility: public
12+
LocalGeneratedAssetHandler:
13+
class: 'SilverStripe\Assets\Flysystem\GeneratedAssets'
14+
properties:
15+
Filesystem: '%$League\Flysystem\Filesystem.localpublic'
16+
SilverStripe\Forms\HTMLEditor\TinyMCECombinedGenerator:
17+
properties:
18+
AssetHandler: '%$LocalGeneratedAssetHandler'

composer.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "fullscreeninteractive/silverstripe-azure-blob-storage",
3+
"type": "silverstripe-vendormodule",
4+
"description": "Adds Silverstripe Flysystem support for using the Azure Blob adapter",
5+
"license": "BSD-3-Clause",
6+
"keywords": [
7+
"silverstripe",
8+
"flysystem",
9+
"azure"
10+
],
11+
"authors": [
12+
{
13+
"name": "Will Rossiter",
14+
"homepage": "https://www.fullscreen.io"
15+
}
16+
],
17+
"require": {
18+
"silverstripe/framework": "^4",
19+
"silverstripe/vendor-plugin": "^1.0",
20+
"league/flysystem-azure-blob-storage": "^1.0",
21+
"league/flysystem-cached-adapter": "^1.0"
22+
},
23+
"autoload": {
24+
"psr-4": {
25+
"FullscreenInteractive\\SilverStripe\\AzureStorage\\": "src/"
26+
}
27+
},
28+
"minimum-stability": "dev",
29+
"prefer-stable": true
30+
}

src/Adapter/ProtectedAdapter.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
namespace FullscreenInteractive\SilverStripe\AzureStorage\Adapter;
4+
5+
use Exception;
6+
use InvalidArgumentException;
7+
use League\Flysystem\AzureBlobStorage\AzureBlobStorageAdapter;
8+
use MicrosoftAzure\Storage\Blob\BlobRestProxy;
9+
use SilverStripe\Assets\Flysystem\ProtectedAdapter as SilverstripeProtectedAdapter;
10+
11+
class ProtectedAdapter extends AzureBlobStorageAdapter implements SilverstripeProtectedAdapter
12+
{
13+
/**
14+
* Pre-signed request expiration time in seconds, or relative string
15+
*
16+
* @var int|string
17+
*/
18+
protected $expiry = 300;
19+
20+
public function __construct($connectionUrl = '', $containerName = '')
21+
{
22+
if (!$connectionUrl) {
23+
throw new InvalidArgumentException("AZURE_CONNECTION_URL environment variable not set");
24+
}
25+
26+
if (!$containerName) {
27+
throw new InvalidArgumentException("AZURE_CONTAINER_NAME environment variable not set");
28+
}
29+
30+
$client = BlobRestProxy::createBlobService($connectionUrl);
31+
32+
parent::__construct($client, $containerName);
33+
}
34+
35+
/**
36+
* @return int|string
37+
*/
38+
public function getExpiry()
39+
{
40+
return $this->expiry;
41+
}
42+
43+
/**
44+
* Set expiry. Supports either number of seconds (in int) or
45+
* a literal relative string.
46+
*
47+
* @param int|string $expiry
48+
* @return $this
49+
*/
50+
public function setExpiry($expiry)
51+
{
52+
$this->expiry = $expiry;
53+
return $this;
54+
}
55+
56+
/**
57+
* @param string $path
58+
*
59+
* @return string
60+
*/
61+
public function getProtectedUrl($path)
62+
{
63+
throw new Exception('Not implemented yet');
64+
}
65+
66+
public function getVisibility($path)
67+
{
68+
// Save an API call
69+
return [
70+
'path' => $path,
71+
'visibility' => self::VISIBILITY_PRIVATE
72+
];
73+
}
74+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace FullscreenInteractive\SilverStripe\AzureStorage\Adapter;
4+
5+
use League\Flysystem\Cached\CachedAdapter;
6+
use SilverStripe\Assets\Flysystem\ProtectedAdapter;
7+
8+
class ProtectedCachedAdapter extends CachedAdapter implements ProtectedAdapter
9+
{
10+
public function getProtectedUrl($path)
11+
{
12+
return $this->getAdapter()->getProtectedUrl($path);
13+
}
14+
}

src/Adapter/PublicAdapter.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace FullscreenInteractive\SilverStripe\AzureStorage\Adapter;
4+
5+
use InvalidArgumentException;
6+
use League\Flysystem\AzureBlobStorage\AzureBlobStorageAdapter;
7+
use MicrosoftAzure\Storage\Blob\BlobRestProxy;
8+
use SilverStripe\Assets\Flysystem\PublicAdapter as SilverstripePublicAdapter;
9+
10+
class PublicAdapter extends AzureBlobStorageAdapter implements SilverstripePublicAdapter
11+
{
12+
public function __construct($connectionUrl = '', $containerName = '')
13+
{
14+
if (!$connectionUrl) {
15+
throw new InvalidArgumentException("AZURE_CONNECTION_URL environment variable not set");
16+
}
17+
18+
if (!$containerName) {
19+
throw new InvalidArgumentException("AZURE_CONTAINER_NAME environment variable not set");
20+
}
21+
22+
$client = BlobRestProxy::createBlobService($connectionUrl);
23+
24+
parent::__construct($client, $containerName);
25+
}
26+
27+
/**
28+
* @param string $path
29+
*
30+
* @return string
31+
*/
32+
public function getPublicUrl($path)
33+
{
34+
if ($meta = $this->getMetadata($path)) {
35+
return $meta['url'];
36+
}
37+
38+
return '';
39+
}
40+
}

0 commit comments

Comments
 (0)