| 1 |
<?php |
| 2 |
|
| 3 |
if ( ! defined( 'ABSPATH' ) ) { |
| 4 |
exit; |
| 5 |
} |
| 6 |
|
| 7 |
if( !class_exists('FV_Player_DigitalOcean_Spaces') ) : |
| 8 |
|
| 9 |
class FV_Player_DigitalOcean_Spaces extends FV_Player_CDN { |
| 10 |
|
| 11 |
function __construct() { |
| 12 |
|
| 13 |
if ( ! defined( 'ABSPATH' ) ) { |
| 14 |
exit; |
| 15 |
} |
| 16 |
|
| 17 |
// TODO: What if FV Player is not yet loaded? |
| 18 |
parent::__construct( array( 'key' => 'digitalocean_spaces', 'title' => 'DigitalOcean Spaces') ); |
| 19 |
|
| 20 |
// we use priority of 9 to make sure it's loaded before FV Player Pro would load it |
| 21 |
add_action( 'plugins_loaded', array( $this, 'include_dos_media_browser' ), 9 ); |
| 22 |
add_action( 'admin_init', array( $this, 'remove_fv_player_pro_dos' ), 21 ); |
| 23 |
add_action( 'admin_init', array( $this, 'migrate_fv_player_pro_dos' ), 21 ); |
| 24 |
} |
| 25 |
|
| 26 |
// includes the Digital Ocean Spaces handling class itself |
| 27 |
public function include_dos_media_browser() { |
| 28 |
if ( is_admin() && version_compare(phpversion(),'7.4') != -1 ) { |
| 29 |
include( dirname( __FILE__ ) . '/digitalocean-spaces-browser.class.php' ); |
| 30 |
} |
| 31 |
} |
| 32 |
|
| 33 |
function get_endpoint() { |
| 34 |
global $fv_fp; |
| 35 |
$parsed = wp_parse_url( $fv_fp->_get_option( array($this->key,'endpoint' ) ) ); |
| 36 |
|
| 37 |
if( count($parsed) == 1 && !empty($parsed['path']) ) { // for input like "region.digitaloceanspaces.com" it returns it as path, not realizing it's the hostname |
| 38 |
return $parsed['path']; |
| 39 |
|
| 40 |
} else if( !empty($parsed['host']) ) { |
| 41 |
return $parsed['host']; |
| 42 |
|
| 43 |
} |
| 44 |
return false; |
| 45 |
} |
| 46 |
|
| 47 |
function get_space() { |
| 48 |
global $fv_fp; |
| 49 |
$space = $fv_fp->_get_option( array($this->key,'space' ) ); |
| 50 |
|
| 51 |
// If multiple DigitalOcean Spaces are configured, use the first one |
| 52 |
$spaces = explode( ',', $space ); |
| 53 |
$space = $spaces[0]; |
| 54 |
|
| 55 |
return $space; |
| 56 |
} |
| 57 |
|
| 58 |
function get_domains() { |
| 59 |
global $fv_fp; |
| 60 |
$spaces = $fv_fp->_get_option( array($this->key,'space' ) ); |
| 61 |
$spaces = explode( ',', $spaces ); |
| 62 |
|
| 63 |
$endpoint = $this->get_endpoint(); |
| 64 |
|
| 65 |
$domains = array(); |
| 66 |
|
| 67 |
foreach ( $spaces as $space ) { |
| 68 |
if( $space && $endpoint ) { |
| 69 |
/** |
| 70 |
* TODO: These two domains are really a problem. We do not store the endpoint for each Space, so here we just assume any. |
| 71 |
* It's used in case of redundant Spaces which are not properly defined yet. |
| 72 |
*/ |
| 73 |
$domains[] = $space . '.'; |
| 74 |
$domains[] = '/' . $space; |
| 75 |
|
| 76 |
$domains[] = $space . '.' . $endpoint; |
| 77 |
$domains[] = $endpoint . '/' . $space; |
| 78 |
} |
| 79 |
} |
| 80 |
|
| 81 |
if ( count( $domains ) ) { |
| 82 |
return $domains; |
| 83 |
} else { |
| 84 |
return false; |
| 85 |
} |
| 86 |
} |
| 87 |
|
| 88 |
function get_region() { |
| 89 |
$parts = explode( '.', $this->get_endpoint() ); |
| 90 |
return $parts[0]; |
| 91 |
} |
| 92 |
|
| 93 |
function get_secure_tokens() { |
| 94 |
global $fv_fp; |
| 95 |
return array( $fv_fp->_get_option( array($this->key,'secret' ) ) ); |
| 96 |
} |
| 97 |
|
| 98 |
/* |
| 99 |
* Migrate DigitalOcean Spaces settings from FV Player Pro |
| 100 |
*/ |
| 101 |
function migrate_fv_player_pro_dos() { |
| 102 |
$option = get_option('fvwpflowplayer'); |
| 103 |
|
| 104 |
$found_anything = false; |
| 105 |
|
| 106 |
global $fv_fp; |
| 107 |
foreach( array( |
| 108 |
'endpoint', |
| 109 |
'secret', |
| 110 |
'key', |
| 111 |
'space' |
| 112 |
) AS $key ) { |
| 113 |
|
| 114 |
// bail if we have something already |
| 115 |
if( !empty($option['digitalocean_spaces']) && !empty($option['digitalocean_spaces'][$key]) ) continue; |
| 116 |
|
| 117 |
if( $value = $fv_fp->_get_option( array( 'pro', 'digitalocean_spaces_'.$key ) ) ) { |
| 118 |
if( empty($option['digitalocean_spaces']) ) $option['digitalocean_spaces'] = array(); |
| 119 |
|
| 120 |
$option['digitalocean_spaces'][$key] = $value; |
| 121 |
|
| 122 |
$found_anything = true; |
| 123 |
} |
| 124 |
} |
| 125 |
|
| 126 |
if( $found_anything ) { |
| 127 |
update_option('fvwpflowplayer', $option); |
| 128 |
} |
| 129 |
|
| 130 |
} |
| 131 |
|
| 132 |
function options() { |
| 133 |
// TODO: Fix width |
| 134 |
// TODO: Add custom domain for CDN |
| 135 |
global $fv_fp; |
| 136 |
?> |
| 137 |
<table class="form-table2" style="margin: 5px; "> |
| 138 |
<?php |
| 139 |
$fv_fp->_get_input_text( array( |
| 140 |
'key' => array($this->key,'space'), |
| 141 |
'name' => 'Space Name', |
| 142 |
'first_td_class' => 'first' |
| 143 |
) ); |
| 144 |
$fv_fp->_get_input_text( array( |
| 145 |
'key' => array($this->key,'endpoint'), |
| 146 |
'name' => 'Endpoint' |
| 147 |
) ); |
| 148 |
$fv_fp->_get_input_text( array( |
| 149 |
'key' => array($this->key,'key'), |
| 150 |
'name' => 'Key' |
| 151 |
) ); |
| 152 |
$fv_fp->_get_input_text( array( |
| 153 |
'key' => array($this->key,'secret'), |
| 154 |
'name' => 'Secret', |
| 155 |
'secret' => true |
| 156 |
) ); |
| 157 |
?> |
| 158 |
<tr> |
| 159 |
<td colspan="4"> |
| 160 |
<a class="fv-wordpress-flowplayer-save button button-primary" href="#" style="margin-top: 2ex;"><?php esc_html_e( 'Save', 'fv-player' ); ?></a> |
| 161 |
</td> |
| 162 |
</tr> |
| 163 |
</table> |
| 164 |
<?php |
| 165 |
} |
| 166 |
|
| 167 |
function remove_fv_player_pro_dos() { |
| 168 |
// remove the legacy settings box in FV Player Pro |
| 169 |
remove_meta_box('fv_player_pro_digitalocean_spaces', 'fv_flowplayer_settings_hosting', 'normal'); |
| 170 |
} |
| 171 |
|
| 172 |
function secure_link( $url, $secret, $ttl = false ) { |
| 173 |
|
| 174 |
if( stripos($url,'X-Amz-Expires') !== false ) return $url; |
| 175 |
|
| 176 |
global $fv_fp; |
| 177 |
$key = $fv_fp->_get_option( array($this->key,'key' ) ); |
| 178 |
$secret = $fv_fp->_get_option( array($this->key,'secret' ) ); |
| 179 |
$endpoint = $fv_fp->_get_option( array($this->key,'endpoint' ) ); |
| 180 |
$endpoint = explode('.',$endpoint); |
| 181 |
$endpoint = $endpoint[0]; |
| 182 |
|
| 183 |
$time = $ttl ? $ttl : apply_filters('fv_player_secure_link_timeout', 900); |
| 184 |
|
| 185 |
$url_components = wp_parse_url($url); |
| 186 |
|
| 187 |
$url_components['path'] = str_replace( array('%20','+'), ' ', $url_components['path']); |
| 188 |
|
| 189 |
$url_components['path'] = rawurlencode($url_components['path']); |
| 190 |
$url_components['path'] = str_replace('%2F', '/', $url_components['path']); |
| 191 |
$url_components['path'] = str_replace('%2B', '+', $url_components['path']); |
| 192 |
$url_components['path'] = str_replace('%2523', '%23', $url_components['path']); |
| 193 |
$url_components['path'] = str_replace('%252B', '%2B', $url_components['path']); |
| 194 |
$url_components['path'] = str_replace('%2527', '%27', $url_components['path']); |
| 195 |
|
| 196 |
// Round the current time to the nearest $time interval to ensure consistent signatures |
| 197 |
// Use ceil() to ensure URLs are valid for the full $time duration |
| 198 |
$currentTime = time(); |
| 199 |
$roundedTime = ceil($currentTime / $time) * $time; |
| 200 |
|
| 201 |
$sXAMZDate = gmdate('Ymd\THis\Z', $roundedTime); |
| 202 |
$sDate = gmdate('Ymd', $roundedTime); |
| 203 |
$sCredentialScope = $sDate."/".$endpoint."/s3/aws4_request"; // todo: variable |
| 204 |
$sSignedHeaders = "host"; |
| 205 |
$sXAMZCredential = urlencode( $key.'/'.$sCredentialScope); |
| 206 |
|
| 207 |
// Support DigitalOcean Spaces CDN |
| 208 |
$url_components['host'] = str_replace( 'cdn.', '', $url_components['host'] ); |
| 209 |
|
| 210 |
// 1. http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html |
| 211 |
$sCanonicalRequest = "GET\n"; |
| 212 |
$sCanonicalRequest .= $url_components['path']."\n"; |
| 213 |
$sCanonicalRequest .= "X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=$sXAMZCredential&X-Amz-Date=$sXAMZDate&X-Amz-Expires=$time&X-Amz-SignedHeaders=$sSignedHeaders\n"; |
| 214 |
$sCanonicalRequest .= "host:".$url_components['host']."\n"; |
| 215 |
$sCanonicalRequest .= "\n$sSignedHeaders\n"; |
| 216 |
$sCanonicalRequest .= "UNSIGNED-PAYLOAD"; |
| 217 |
|
| 218 |
// 2. http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html |
| 219 |
$sStringToSign = "AWS4-HMAC-SHA256\n"; |
| 220 |
$sStringToSign .= "$sXAMZDate\n"; |
| 221 |
$sStringToSign .= "$sCredentialScope\n"; |
| 222 |
$sStringToSign .= hash('sha256',$sCanonicalRequest); |
| 223 |
|
| 224 |
// 3. http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html |
| 225 |
$sSignature = hash_hmac('sha256', $sDate, "AWS4".$secret, true ); |
| 226 |
$sSignature = hash_hmac('sha256', $endpoint, $sSignature, true ); // todo: variable |
| 227 |
$sSignature = hash_hmac('sha256', 's3', $sSignature, true ); |
| 228 |
$sSignature = hash_hmac('sha256', 'aws4_request', $sSignature, true ); |
| 229 |
$sSignature = hash_hmac('sha256', $sStringToSign, $sSignature ); |
| 230 |
|
| 231 |
// 4. http://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html |
| 232 |
$url .= "?X-Amz-Algorithm=AWS4-HMAC-SHA256"; |
| 233 |
$url .= "&X-Amz-Credential=$sXAMZCredential"; |
| 234 |
$url .= "&X-Amz-Date=$sXAMZDate"; |
| 235 |
$url .= "&X-Amz-Expires=$time"; |
| 236 |
$url .= "&X-Amz-SignedHeaders=$sSignedHeaders"; |
| 237 |
$url .= "&X-Amz-Signature=".$sSignature; |
| 238 |
|
| 239 |
return $url; |
| 240 |
} |
| 241 |
|
| 242 |
} |
| 243 |
|
| 244 |
global $FV_Player_DigitalOcean_Spaces; |
| 245 |
$FV_Player_DigitalOcean_Spaces = new FV_Player_DigitalOcean_Spaces; |
| 246 |
|
| 247 |
endif; |
| 248 |
|