PluginProbe
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions / trunk
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions vtrunk
260909 260829 260814 260805 110710 110731 110812 110815 110912 110913 110915 110926 110927 111002 111003 111011 111017 111029 111105 111206 111216 111220 120213 120219 120301 All 187 releases
s2member / src / includes / classes / files-in.inc.php

files-in.inc.php in s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions trunk, at src/includes/classes/files-in.inc.php

1,604 lines 120.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // @codingStandardsIgnoreFile
3 /**
4 * File Download routines for s2Member (inner processing routines).
5 *
6 * Copyright: © 2009-2011
7 * {@link http://websharks-inc.com/ WebSharks, Inc.}
8 * (coded in the USA)
9 *
10 * Released under the terms of the GNU General Public License.
11 * You should have received a copy of the GNU General Public License,
12 * along with this software. In the main directory, see: /licensing/
13 * If not, see: {@link http://www.gnu.org/licenses/}.
14 *
15 * @package s2Member\Files
16 * @since 3.5
17 */
18 if(!defined('WPINC')) // MUST have WordPress.
19 exit('Do not access this file directly.');
20
21 if(!class_exists('c_ws_plugin__s2member_files_in'))
22 {
23 /**
24 * File Download routines for s2Member (inner processing routines).
25 *
26 * @package s2Member\Files
27 * @since 3.5
28 */
29 class c_ws_plugin__s2member_files_in
30 {
31 /**
32 * Handles Download Access permissions.
33 *
34 * @package s2Member\Files
35 * @since 3.5
36 *
37 * @attaches-to ``add_action('init');``
38 * @also-called-by API Function {@link s2Member\API_Functions\s2member_file_download_url()}, w/ ``$create_file_download_url`` param.
39 *
40 * @param null|array $create_file_download_url Optional. If this function is called directly, we can pass arguments through this array.
41 * Possible array elements: `file_download` *(required)*, `file_download_key`, `file_stream`, `file_inline`, `file_storage`, `file_remote`, `file_ssl`, `file_rewrite`, `file_rewrite_base`, `skip_confirmation`, `url_to_storage_source`, `count_against_user`, `check_user`.
42 *
43 * @return null|string If called directly with ``$create_file_download_url``, returns a string with the URL, based on configuration.
44 * Else, this function may exit script execution after serving a File Download.
45 */
46 public static function check_file_download_access($create_file_download_url = NULL)
47 {
48 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
49 do_action('ws_plugin__s2member_before_file_download_access', get_defined_vars());
50 unset($__refs, $__v); // Housekeeping.
51
52 $_g = !empty($_GET) ? $_GET : array();
53 $_g = c_ws_plugin__s2member_utils_strings::trim_deep(stripslashes_deep($_g));
54
55 $creating = (is_array($create = $create_file_download_url)) ? TRUE : FALSE; // Creating URL?
56 $serving = (!$creating) ? TRUE : FALSE; // If NOT creating a File Download URL, we're serving one.
57 $serving_range = $range = FALSE; // Default values (so these variables DO get defined at all times).
58
59 if($serving) // If we're serving, let's see if we're serving a byte-range request here.
60 {
61 $range = (string)@$_SERVER['HTTP_RANGE'];
62
63 if(!$range && function_exists('apache_request_headers'))
64 {
65 foreach((array)apache_request_headers() as $_header => $_value)
66 // Note: ``apache_request_headers()`` works in FastCGI too, starting w/ PHP v5.4.
67 if(is_string($_header) && strcasecmp($_header, 'range') === 0)
68 $range = $_value;
69 }
70 unset($_header, $_value); // Housekeeping.
71
72 if($range) $serving_range = TRUE;
73 }
74 $req['file_download'] = ($creating) ? @$create['file_download'] : @$_g['s2member_file_download'];
75 $req['file_download_key'] = ($creating) ? @$create['file_download_key'] : @$_g['s2member_file_download_key'];
76
77 $req['file_stream'] = ($creating) ? @$create['file_stream'] : @$_g['s2member_file_stream'];
78 $req['file_inline'] = ($creating) ? @$create['file_inline'] : @$_g['s2member_file_inline'];
79 $req['file_storage'] = ($creating) ? @$create['file_storage'] : @$_g['s2member_file_storage'];
80 $req['file_remote'] = ($creating) ? @$create['file_remote'] : @$_g['s2member_file_remote'];
81 $req['file_ssl'] = ($creating) ? @$create['file_ssl'] : @$_g['s2member_file_ssl'];
82
83 $req['file_rewrite'] = ($creating) ? @$create['file_rewrite'] : NULL;
84 $req['file_rewrite_base'] = ($creating) ? @$create['file_rewrite_base'] : NULL;
85
86 $req['skip_confirmation'] = ($creating) ? @$create['skip_confirmation'] : NULL;
87 $req['url_to_storage_source'] = ($creating) ? @$create['url_to_storage_source'] : NULL;
88 $req['count_against_user'] = ($creating) ? @$create['count_against_user'] : NULL;
89 $req['check_user'] = ($creating) ? @$create['check_user'] : NULL;
90
91 if($req['file_download'] && is_string($req['file_download']) && ($req['file_download'] = trim($req['file_download'], '/')))
92 if(strpos($req['file_download'], '..') === FALSE && strpos(basename($req['file_download']), '.') !== 0)
93 {
94 $using_amazon_cf_storage = ((!$req['file_storage'] || strcasecmp((string)$req['file_storage'], 'cf') === 0) && c_ws_plugin__s2member_utils_conds::using_amazon_cf_storage()) ? TRUE : FALSE;
95 $using_amazon_s3_storage = ((!$req['file_storage'] || strcasecmp((string)$req['file_storage'], 's3') === 0) && c_ws_plugin__s2member_utils_conds::using_amazon_s3_storage()) ? TRUE : FALSE;
96 $using_amazon_storage = ($using_amazon_cf_storage || $using_amazon_s3_storage) ? TRUE : FALSE;
97
98 $excluded = apply_filters('ws_plugin__s2member_check_file_download_access_excluded', FALSE, get_defined_vars());
99 $valid_file_download_key = ($req['file_download_key'] && is_string($req['file_download_key']) && $creating && (!isset($req['check_user']) || !filter_var($req['check_user'], FILTER_VALIDATE_BOOLEAN)) && (!isset($req['count_against_user']) || !filter_var($req['count_against_user'], FILTER_VALIDATE_BOOLEAN))) ? TRUE : FALSE;
100 $valid_file_download_key = (!$valid_file_download_key && $req['file_download_key'] && is_string($req['file_download_key'])) ? c_ws_plugin__s2member_files_in::check_file_download_key($req['file_download'], $req['file_download_key']) : FALSE;
101 $checking_user = ($excluded || $valid_file_download_key || ($creating && (!isset($req['check_user']) || !filter_var($req['check_user'], FILTER_VALIDATE_BOOLEAN)) && (!isset($req['count_against_user']) || !filter_var($req['count_against_user'], FILTER_VALIDATE_BOOLEAN)))) ? FALSE : TRUE;
102 $updating_user_counter = ($serving_range || !$checking_user || ($creating && (!isset($req['count_against_user']) || !filter_var($req['count_against_user'], FILTER_VALIDATE_BOOLEAN)))) ? FALSE : TRUE;
103
104 if(($serving || $creating) && $checking_user) // In either case, the following routines apply whenever we ARE ``$checking_user``.
105 {
106 if(!$using_amazon_storage && !file_exists($GLOBALS['WS_PLUGIN__']['s2member']['c']['files_dir'].'/'.$req['file_download']))
107 {
108 if($serving) // We only need this section when/if we're actually serving.
109 {
110 status_header(404);
111 header('Content-Type: text/html; charset=UTF-8');
112 while(@ob_end_clean()) ; // Clean any existing output buffers.
113 exit(_x('<strong>404: Sorry, file not found.</strong> Please contact Support for assistance.', 's2member-front', 's2member'));
114 }
115 return FALSE; // Else return false.
116 }
117 else if($req['file_download_key'] && is_string($req['file_download_key']) && !$valid_file_download_key)
118 {
119 if($serving) // We only need this section when/if we're actually serving.
120 {
121 status_header(503);
122 header('Content-Type: text/html; charset=UTF-8');
123 while(@ob_end_clean()) ; // Clean any existing output buffers.
124 exit(_x('<strong>503 (Invalid Key):</strong> Sorry, your access to this file has expired. Please contact Support for assistance.', 's2member-front', 's2member'));
125 }
126 return FALSE; // Else return false.
127 }
128 else // Default behavior; check file download access against the current user.
129 {
130 if($serving) // We only need remote functionality when/if we're actually serving.
131 if(!has_filter('ws_plugin__s2member_check_file_download_access_user', 'c_ws_plugin__s2member_files_in::check_file_remote_authorization'))
132 add_filter('ws_plugin__s2member_check_file_download_access_user', 'c_ws_plugin__s2member_files_in::check_file_remote_authorization', 10, 2);
133
134 if($creating) // We only need remote functionality when/if we're actually serving.
135 if(has_filter('ws_plugin__s2member_check_file_download_access_user', 'c_ws_plugin__s2member_files_in::check_file_remote_authorization'))
136 remove_filter('ws_plugin__s2member_check_file_download_access_user', 'c_ws_plugin__s2member_files_in::check_file_remote_authorization', 10, 2);
137
138 if(!$GLOBALS['WS_PLUGIN__']['s2member']['o']['membership_options_page'])
139 {
140 if($serving) // We only need this section when/if we're actually serving.
141 {
142 status_header(503);
143 header('Content-Type: text/html; charset=UTF-8');
144 while(@ob_end_clean()) ; // Clean any existing output buffers.
145 exit(_x('<strong>503: Basic File Downloads are NOT enabled yet.</strong> Please contact Support for assistance. If you are the site owner, please configure: <strong>s2Member → General Options → Membership Options Page</strong>.', 's2member-front', 's2member'));
146 }
147 return FALSE; // Else return false.
148 }
149 else if(($file_downloads_enabled_by_site_owner = $min_level_4_downloads = c_ws_plugin__s2member_files::min_level_4_downloads()) === FALSE)
150 {
151 if($serving) // We only need this section when/if we're actually serving.
152 {
153 status_header(503);
154 header('Content-Type: text/html; charset=UTF-8');
155 while(@ob_end_clean()) ; // Clean any existing output buffers.
156 exit(_x('<strong>503: Basic File Downloads are NOT enabled yet.</strong> Please contact Support for assistance. If you are the site owner, please configure: <strong>s2Member → Download Options → Basic Download Restrictions</strong>.', 's2member-front', 's2member'));
157 }
158 return FALSE; // Else return false.
159 }
160 else if(!is_object($user = apply_filters('ws_plugin__s2member_check_file_download_access_user', ((is_user_logged_in()) ? wp_get_current_user() : FALSE), get_defined_vars())) || empty($user->ID) || !($user_id = $user->ID) || !is_array($user_file_downloads = c_ws_plugin__s2member_files::user_downloads($user)) || (!$user->has_cap('administrator') && (!$user_file_downloads['allowed'] || !$user_file_downloads['allowed_days'])))
161 {
162 if(preg_match('/(?:^|\/)access[_\-]s2member[_\-]level([0-9]+)\//', $req['file_download'], $m) && strlen($req_level = $m[1]) && (!is_object($user) || empty($user->ID) || !$user->has_cap('access_s2member_level'.$req_level)))
163 {
164 if($serving) // We only need this section when/if we're actually serving.
165 c_ws_plugin__s2member_mo_page::wp_redirect_w_mop_vars('file', $req['file_download'], 'level', $req_level, $_SERVER['REQUEST_URI']).exit();
166
167 return FALSE; // Else return false.
168 }
169 else if(preg_match('/(?:^|\/)access[_\-]s2member[_\-]ccap[_\-](.+?)\//', $req['file_download'], $m) && strlen($req_ccap = preg_replace('/-/', '_', $m[1])) && (!is_object($user) || empty($user->ID) || !$user->has_cap('access_s2member_ccap_'.$req_ccap)))
170 {
171 if($serving) // We only need this section when/if we're actually serving.
172 c_ws_plugin__s2member_mo_page::wp_redirect_w_mop_vars('file', $req['file_download'], 'ccap', $req_ccap, $_SERVER['REQUEST_URI']).exit();
173
174 return FALSE; // Else return false.
175 }
176 else if($serving) // We only need this section when/if we're actually serving.
177 c_ws_plugin__s2member_mo_page::wp_redirect_w_mop_vars('file', $req['file_download'], 'level', $min_level_4_downloads, $_SERVER['REQUEST_URI']).exit();
178
179 return FALSE; // Else return false.
180 }
181 else if(preg_match('/(?:^|\/)access[_\-]s2member[_\-]level([0-9]+)\//', $req['file_download'], $m) && strlen($req_level = $m[1]) && !$user->has_cap('access_s2member_level'.$req_level))
182 {
183 if($serving) // We only need this section when/if we're actually serving.
184 c_ws_plugin__s2member_mo_page::wp_redirect_w_mop_vars('file', $req['file_download'], 'level', $req_level, $_SERVER['REQUEST_URI']).exit();
185
186 return FALSE; // Else return false.
187 }
188 else if(preg_match('/(?:^|\/)access[_\-]s2member[_\-]ccap[_\-](.+?)\//', $req['file_download'], $m) && strlen($req_ccap = preg_replace('/-/', '_', $m[1])) && !$user->has_cap('access_s2member_ccap_'.$req_ccap))
189 {
190 if($serving) // We only need this section when/if we're actually serving.
191 c_ws_plugin__s2member_mo_page::wp_redirect_w_mop_vars('file', $req['file_download'], 'ccap', $req_ccap, $_SERVER['REQUEST_URI']).exit();
192
193 return FALSE; // Else return false.
194 }
195 else if($serving || $creating) // In either case, the following routines apply.
196 {
197 $user_previous_file_downloads = 0; // Downloads the User has already; in current period/cycle.
198 $user_already_downloaded_this_file = $user_already_downloaded_a_streaming_variation_of_this_file = FALSE;
199
200 $user_file_download_access_log = (is_array($user_file_download_access_log = get_user_option('s2member_file_download_access_log', $user_id))) ? $user_file_download_access_log : array();
201 $user_file_download_access_arc = (is_array($user_file_download_access_arc = get_user_option('s2member_file_download_access_arc', $user_id))) ? $user_file_download_access_arc : array();
202
203 $streaming_file_extns = c_ws_plugin__s2member_utils_strings::preg_quote_deep($GLOBALS['WS_PLUGIN__']['s2member']['c']['streaming_file_extns'], '/');
204 $streaming_variations = '/\.('.implode('|', $streaming_file_extns).')$/i'; // Only count one streaming media file variation.
205
206 foreach($user_file_download_access_log as $user_file_download_access_log_entry_key => $user_file_download_access_log_entry)
207 {
208 if(isset($user_file_download_access_log_entry['date'], $user_file_download_access_log_entry['file'])) // Weed out corrupt/empty log entries.
209 {
210 if(strtotime($user_file_download_access_log_entry['date']) < strtotime('-'.$user_file_downloads['allowed_days'].' days'))
211 {
212 unset($user_file_download_access_log[$user_file_download_access_log_entry_key]); // Remove it from the `log`.
213 $user_file_download_access_arc[] = $user_file_download_access_log_entry; // Move `log` entry to the `archive` now.
214 }
215 else if(strtotime($user_file_download_access_log_entry['date']) >= strtotime('-'.$user_file_downloads['allowed_days'].' days'))
216 {
217 $user_previous_file_downloads++; // Previous files always count against this User/Member.
218
219 $_user_file_download_access_log_entry = &$user_file_download_access_log[$user_file_download_access_log_entry_key];
220 $_user_already_downloaded_this_file = $_user_already_downloaded_a_streaming_variation_of_this_file = FALSE;
221
222 if($user_file_download_access_log_entry['file'] === $req['file_download']) // Already downloaded this file? If yes, mark this flag as true.
223 $user_already_downloaded_this_file = $_user_already_downloaded_this_file = TRUE; // Already downloaded this file? If yes, mark as true.
224
225 else if(preg_replace($streaming_variations, '', $user_file_download_access_log_entry['file']) === preg_replace($streaming_variations, '', $req['file_download']))
226 $user_already_downloaded_this_file = $_user_already_downloaded_this_file = $user_already_downloaded_a_streaming_variation_of_this_file = $_user_already_downloaded_a_streaming_variation_of_this_file = TRUE;
227
228 if($updating_user_counter && ($_user_already_downloaded_this_file || $_user_already_downloaded_a_streaming_variation_of_this_file)) // Updating counter?
229 {
230 $_user_file_download_access_log_entry['ltime'] = time(); // First, we update the last download time for this file.
231
232 if(!empty($user_file_download_access_log_entry['counter'])) // Backward compatibility here. Is this even set?
233 $_user_file_download_access_log_entry['counter'] = (int)$user_file_download_access_log_entry['counter'] + 1;
234 else // Backward compatibility here. Default value to `1`, if this is NOT even set yet.
235 $_user_file_download_access_log_entry['counter'] = 1 + 1;
236 }
237 }
238 }
239 else // Weed out empty log entries. Some older versions of s2Member may have corrupt/empty log entries.
240 unset($user_file_download_access_log[$user_file_download_access_log_entry_key]); // Remove.
241 }
242 if($updating_user_counter && !$user_already_downloaded_this_file && !$user_already_downloaded_a_streaming_variation_of_this_file) // Do we need a new log entry for this file?
243 $user_file_download_access_log[] = array('date' => date('Y-m-d'), 'time' => time(), 'ltime' => time(), 'file' => $req['file_download'], 'counter' => 1);
244
245 if($user_previous_file_downloads >= $user_file_downloads['allowed'] && !$user_already_downloaded_this_file && !$user_already_downloaded_a_streaming_variation_of_this_file && !$user->has_cap('administrator'))
246 {
247 if($serving) // We only need this section when/if we're actually serving.
248 wp_redirect(add_query_arg(urlencode_deep(array('_s2member_seeking' => array('type' => 'file', 'file' => $req['file_download'], '_uri' => base64_encode($_SERVER['REQUEST_URI'])), 's2member_seeking' => 'file-'.$req['file_download'])), get_page_link($GLOBALS['WS_PLUGIN__']['s2member']['o']['file_download_limit_exceeded_page'])), apply_filters('ws_plugin__s2member_content_redirect_status', 301, get_defined_vars())).exit();
249
250 return FALSE; // Else return false.
251 }
252 else if($updating_user_counter) // Save/update counter? By default, we do NOT update the counter when a URL is simply being created for access.
253 update_user_option($user_id, 's2member_file_download_access_log', c_ws_plugin__s2member_utils_arrays::array_unique($user_file_download_access_log)).update_user_option($user_id, 's2member_file_download_access_arc', c_ws_plugin__s2member_utils_arrays::array_unique($user_file_download_access_arc));
254 }
255 }
256 }
257 else // Otherwise, we're either NOT ``$checking_user``; or permission was granted with a valid File Download Key.
258 {
259 if(!$using_amazon_storage && !file_exists($GLOBALS['WS_PLUGIN__']['s2member']['c']['files_dir'].'/'.$req['file_download']))
260 {
261 if($serving) // We only need this section when/if we're actually serving.
262 {
263 status_header(404);
264 header('Content-Type: text/html; charset=UTF-8');
265 while(@ob_end_clean()) ; // Clean any existing output buffers.
266 exit(_x('<strong>404: Sorry, file not found.</strong> Please contact Support for assistance.', 's2member-front', 's2member'));
267 }
268 return FALSE; // Else return false.
269 }
270 }
271 if($serving || $creating) // In either case, the following routines apply.
272 {
273 $basename = basename($req['file_download']);
274 $mimetypes = parse_ini_file(dirname(dirname(__FILE__)).'/mime-types.ini');
275 $extension = strtolower(substr($req['file_download'], strrpos($req['file_download'], '.') + 1));
276
277 $key = ($req['file_download_key'] && is_string($req['file_download_key'])) ? $req['file_download_key'] : FALSE;
278
279 $stream = (isset($req['file_stream'])) ? filter_var($req['file_stream'], FILTER_VALIDATE_BOOLEAN) : ((in_array($extension, preg_split('/['."\r\n\t".'\s;,]+/', $GLOBALS['WS_PLUGIN__']['s2member']['o']['file_download_stream_extensions']))) ? TRUE : FALSE);
280 $inline = (!$stream && isset($req['file_inline'])) ? filter_var($req['file_inline'], FILTER_VALIDATE_BOOLEAN) : (($stream || in_array($extension, preg_split('/['."\r\n\t".'\s;,]+/', $GLOBALS['WS_PLUGIN__']['s2member']['o']['file_download_inline_extensions']))) ? TRUE : FALSE);
281 $ssl = (isset($req['file_ssl'])) ? filter_var($req['file_ssl'], FILTER_VALIDATE_BOOLEAN) : ((is_ssl()) ? TRUE : FALSE);
282 $storage = ($req['file_storage'] && is_string($req['file_storage'])) ? strtolower($req['file_storage']) : FALSE;
283 $remote = (isset($req['file_remote'])) ? filter_var($req['file_remote'], FILTER_VALIDATE_BOOLEAN) : FALSE;
284
285 $_basename_dir_app_data = c_ws_plugin__s2member_utils_dirs::basename_dir_app_data($GLOBALS['WS_PLUGIN__']['s2member']['c']['files_dir']);
286 $rewrite_base_guess = (is_dir(dirname($GLOBALS['WS_PLUGIN__']['s2member']['c']['dir']).'/'.$_basename_dir_app_data)) ? dirname($GLOBALS['WS_PLUGIN__']['s2member']['c']['dir_url']).'/'.$_basename_dir_app_data : content_url('/'.$_basename_dir_app_data);
287 $rewrite_base = ($req['file_rewrite_base'] && is_string($req['file_rewrite_base'])) ? $req['file_rewrite_base'] : FALSE;
288 $rewrite = $rewriting = (!$rewrite_base && isset($req['file_rewrite'])) ? filter_var($req['file_rewrite'], FILTER_VALIDATE_BOOLEAN) : (($rewrite_base) ? TRUE : FALSE);
289 unset($_basename_dir_app_data); // A little housekeeping here.
290
291 $skip_confirmation = (isset($req['skip_confirmation'])) ? filter_var($req['skip_confirmation'], FILTER_VALIDATE_BOOLEAN) : FALSE;
292 $url_to_storage_source = (isset($req['url_to_storage_source'])) ? filter_var($req['url_to_storage_source'], FILTER_VALIDATE_BOOLEAN) : FALSE;
293
294 $file = $GLOBALS['WS_PLUGIN__']['s2member']['c']['files_dir'].'/'.$req['file_download'];
295 $pathinfo = (!$using_amazon_storage && $file) ? pathinfo($file) : array();
296 $mimetype = !empty($mimetypes[$extension]) ? $mimetypes[$extension] : 'application/octet-stream';
297 $disposition = (($inline) ? 'inline' : 'attachment').'; filename="'.c_ws_plugin__s2member_utils_strings::esc_dq($basename).'"; filename*=UTF-8\'\''.rawurlencode($basename);
298 $length = (!$using_amazon_storage && $file) ? filesize($file) : -1;
299
300 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
301 do_action('ws_plugin__s2member_during_file_download_access', get_defined_vars());
302 unset($__refs, $__v); // Housekeeping.
303
304 if($using_amazon_storage && $using_amazon_cf_storage && ($serving || ($creating && $url_to_storage_source)))
305 {
306 if($serving) // We only need this section when/if we're actually serving.
307 wp_redirect(c_ws_plugin__s2member_files_in::amazon_cf_url($req['file_download'], $stream, $inline, $ssl, $basename, $mimetype)).exit();
308
309 return apply_filters('ws_plugin__s2member_file_download_access_url', c_ws_plugin__s2member_files_in::amazon_cf_url($req['file_download'], $stream, $inline, $ssl, $basename, $mimetype), get_defined_vars());
310 }
311 else if($using_amazon_storage && $using_amazon_s3_storage && ($serving || ($creating && $url_to_storage_source)))
312 {
313 if($serving) // We only need this section when/if we're actually serving.
314 wp_redirect(c_ws_plugin__s2member_files_in::amazon_s34_url($req['file_download'], $stream, $inline, $ssl, $basename, $mimetype)).exit();
315
316 return apply_filters('ws_plugin__s2member_file_download_access_url', c_ws_plugin__s2member_files_in::amazon_s34_url($req['file_download'], $stream, $inline, $ssl, $basename, $mimetype), get_defined_vars());
317 }
318 else if($creating && $rewriting) // Creating a rewrite URL, pointing to local storage.
319 { // Note: we don't URL encode unreserved chars. Improves media player compatibility.
320
321 $_url_e_key = ($key) ? c_ws_plugin__s2member_utils_strings::urldecode_ur_chars_deep(urlencode($key)) : '';
322 $_url_e_storage = ($storage) ? c_ws_plugin__s2member_utils_strings::urldecode_ur_chars_deep(urlencode($storage)) : '';
323 $_url_e_file = c_ws_plugin__s2member_utils_strings::urldecode_ur_chars_deep(urlencode($req['file_download']));
324 $_url_e_file = str_ireplace('%2F', '/', $_url_e_file);
325
326 $url = ($rewrite_base) ? rtrim($rewrite_base, '/') : rtrim($rewrite_base_guess, '/');
327 $url .= (isset($req['file_download_key'])) ? (($key && $_url_e_key) ? '/s2member-file-download-key-'.$_url_e_key : '') : '';
328 $url .= (isset($req['file_stream'])) ? (($stream) ? '/s2member-file-stream' : '/s2member-file-stream-no') : '';
329 $url .= (isset($req['file_inline'])) ? (($inline) ? '/s2member-file-inline' : '/s2member-file-inline-no') : '';
330 $url .= (isset($req['file_storage'])) ? (($storage && $_url_e_storage) ? '/s2member-file-storage-'.$_url_e_storage : '') : '';
331 $url .= (isset($req['file_remote'])) ? (($remote) ? '/s2member-file-remote' : '/s2member-file-remote-no') : '';
332 $url .= (isset($req['skip_confirmation'])) ? (($skip_confirmation) ? '/s2member-skip-confirmation' : '/s2member-skip-confirmation-no') : '';
333
334 $url = $url.'/'.$_url_e_file; // File Download Access URL via `mod_rewrite` functionality.
335 $url = ($ssl) ? preg_replace('/^https?/', 'https', $url) : preg_replace('/^https?/', 'http', $url);
336
337 return apply_filters('ws_plugin__s2member_file_download_access_url', $url, get_defined_vars());
338 }
339 else if($creating) // Else we're creating a URL w/ a query-string; w/ local storage.
340 { // Note: we don't URL encode unreserved chars. Improves media player compatibility.
341
342 $_url_e_key = ($key) ? c_ws_plugin__s2member_utils_strings::urldecode_ur_chars_deep(urlencode($key)) : '';
343 $_url_e_storage = ($storage) ? c_ws_plugin__s2member_utils_strings::urldecode_ur_chars_deep(urlencode($storage)) : '';
344 $_url_e_file = c_ws_plugin__s2member_utils_strings::urldecode_ur_chars_deep(urlencode($req['file_download']));
345 $_url_e_file = str_ireplace('%2F', '/', $_url_e_file);
346
347 $url = (isset($req['file_download_key'])) ? (($key && $_url_e_key) ? '&s2member_file_download_key='.$_url_e_key : '') : '';
348 $url .= (isset($req['file_stream'])) ? (($stream) ? '&s2member_file_stream=yes' : '&s2member_file_stream=no') : '';
349 $url .= (isset($req['file_inline'])) ? (($inline) ? '&s2member_file_inline=yes' : '&s2member_file_inline=no') : '';
350 $url .= (isset($req['file_storage'])) ? (($storage && $_url_e_storage) ? '&s2member_file_storage='.$_url_e_storage : '') : '';
351 $url .= (isset($req['file_remote'])) ? (($remote) ? '&s2member_file_remote=yes' : '&s2member_file_remote=no') : '';
352 $url .= (isset($req['skip_confirmation'])) ? (($skip_confirmation) ? '&s2member_skip_confirmation=yes' : '&s2member_skip_confirmation=no') : '';
353
354 $url = home_url('/?'.ltrim($url.'&s2member_file_download=/'.$_url_e_file, '&'));
355 $url = ($ssl) ? preg_replace('/^https?/', 'https', $url) : preg_replace('/^https?/', 'http', $url);
356
357 return apply_filters('ws_plugin__s2member_file_download_access_url', $url, get_defined_vars());
358 }
359 else if($serving) // Else, ``if ($serving)``, use local storage.
360 {
361 @set_time_limit(0);
362
363 @ini_set('zlib.output_compression', 0);
364 if(function_exists('apache_setenv'))
365 @apache_setenv('no-gzip', '1');
366
367 $content_encoding_header = 'Content-Encoding:'; // Default value; standards compliant.
368 if($GLOBALS['WS_PLUGIN__']['s2member']['o']['file_download_content_encodong_none'])
369 $content_encoding_header = 'Content-Encoding: none';
370
371 while(@ob_end_clean()) ; // Cleans existing output buffers.
372
373 if($range) // Requesting a specific byte range?
374 {
375 if(strpos($range, '=') === FALSE) // Invalid range?
376 {
377 status_header(416);
378 nocache_headers();
379 header($content_encoding_header);
380 header('Accept-Ranges: bytes');
381 header('Content-Type: '.$mimetype);
382 header('Content-Length: '.$length);
383 header('Content-Disposition: '.$disposition);
384 exit(); // Stop here (invalid).
385 }
386 list($range_type, $byte_range) = preg_split('/\s*\=\s*/', $range, 2);
387
388 $range_type = strtolower(trim($range_type));
389 $byte_range = trim($byte_range);
390
391 if($range_type !== 'bytes') // Invalid range type?
392 {
393 status_header(416);
394 nocache_headers();
395 header($content_encoding_header);
396 header('Accept-Ranges: bytes');
397 header('Content-Type: '.$mimetype);
398 header('Content-Length: '.$length);
399 header('Content-Disposition: '.$disposition);
400 exit(); // Stop here (invalid).
401 }
402 $byte_ranges = preg_split('/\s*,\s*/', $byte_range);
403
404 if(strpos($byte_ranges[0], '-') === FALSE) // Invalid byte range?
405 {
406 status_header(416);
407 nocache_headers();
408 header($content_encoding_header);
409 header('Accept-Ranges: bytes');
410 header('Content-Type: '.$mimetype);
411 header('Content-Length: '.$length);
412 header('Content-Disposition: '.$disposition);
413 exit(); // Stop here (invalid).
414 }
415 // Only dealing with the first byte range. Others are simply ignored here.
416 list($byte_range_start, $byte_range_stops) = preg_split('/\s*\-\s*/', $byte_ranges[0], 2);
417
418 $byte_range_start = trim($byte_range_start);
419 $byte_range_stops = trim($byte_range_stops);
420
421 $byte_range_start = ($byte_range_start === '') ? NULL : (int)$byte_range_start;
422 $byte_range_stops = ($byte_range_stops === '') ? NULL : (int)$byte_range_stops;
423
424 if(!isset($byte_range_start) && $byte_range_stops > 0 && $byte_range_stops <= $length)
425 {
426 $byte_range_start = $length - $byte_range_stops;
427 $byte_range_stops = $length - 1; // The last X number of bytes.
428 }
429 else if(!isset($byte_range_stops) && $byte_range_start >= 0 && $byte_range_start < $length - 1)
430 {
431 $byte_range_stops = $length - 1; // To the end of the file in this case.
432 }
433 else if(isset($byte_range_start, $byte_range_stops) && $byte_range_start >= 0 && $byte_range_start < $length - 1 && $byte_range_stops > $byte_range_start && $byte_range_stops <= $length - 1)
434 {
435 // Nothing to do in this case, starts/stops already defined properly.
436 }
437 else // We have an invalid byte range.
438 {
439 status_header(416);
440 nocache_headers();
441 header($content_encoding_header);
442 header('Accept-Ranges: bytes');
443 header('Content-Type: '.$mimetype);
444 header('Content-Length: '.$length);
445 header('Content-Disposition: '.$disposition);
446 exit(); // Stop here (invalid).
447 }
448 status_header(206);
449 nocache_headers();
450 header($content_encoding_header);
451 header('Accept-Ranges: bytes');
452 header('Content-Type: '.$mimetype);
453 header('Content-Range: bytes '.$byte_range_start.'-'.$byte_range_stops.'/'.$length);
454 $byte_range_size = $byte_range_stops - $byte_range_start + 1;
455 header('Content-Length: '.$byte_range_size);
456 header('Content-Disposition: '.$disposition);
457 }
458 else // A normal request (NOT a specific byte range).
459 {
460 status_header(200);
461 nocache_headers();
462 header($content_encoding_header);
463 header('Accept-Ranges: bytes');
464 header('Content-Type: '.$mimetype);
465 header('Content-Length: '.$length);
466 header('Content-Disposition: '.$disposition);
467 }
468 if(is_resource($resource = fopen($file, 'rb')))
469 {
470 if($range && isset($byte_range_size, $byte_range_start))
471 {
472 $_bytes_to_read = $byte_range_size;
473 fseek($resource, $byte_range_start);
474 }
475 else $_bytes_to_read = $length; // Entire file.
476
477 $chunk_size = apply_filters('ws_plugin__s2member_file_downloads_chunk_size', 2097152, get_defined_vars());
478
479 while($_bytes_to_read > 0) // While we have bytes to read here.
480 {
481 $_bytes_to_read -= ($_reading = ($_bytes_to_read > $chunk_size) ? $chunk_size : $_bytes_to_read);
482 echo fread($resource, $_reading); // Serve file in chunks (default chunk size is 2MB).
483 flush(); // Flush each chunk to the browser as it is served (avoids high memory consumption).
484 }
485 fclose($resource); // Close file resource handle.
486 unset($_bytes_to_read, $_reading); // Housekeeping.
487 }
488 exit(); // Stop execution now (the file has been served).
489 }
490 }
491 }
492 else if($serving && $req['file_download']) // Only when/if serving.
493 {
494 status_header(503);
495 header('Content-Type: text/html; charset=UTF-8');
496 while(@ob_end_clean()) ; // Clean any existing output buffers.
497 exit(_x('<strong>503: Access denied.</strong> Invalid File Download specs.', 's2member-front', 's2member'));
498 }
499 else if($creating) return FALSE; // We only need this section when/if we're creating a URL.
500
501 do_action('ws_plugin__s2member_after_file_download_access', get_defined_vars());
502
503 return ($creating) ? FALSE : NULL; // If creating, false.
504 }
505
506 /**
507 * Generates a File Download URL for access to a file protected by s2Member.
508 *
509 * @package s2Member\Files
510 * @since 110926
511 *
512 * @param array $config Required. This is an array of configuration options associated with permissions being checked against the current User/Member; and also the actual URL generated by this routine.
513 * Possible ``$config`` array elements: `file_download` *(required)*, `file_download_key`, `file_stream`, `file_inline`, `file_storage`, `file_remote`, `file_ssl`, `file_rewrite`, `file_rewrite_base`, `skip_confirmation`, `url_to_storage_source`, `count_against_user`, `check_user`.
514 * @param bool $get_streamer_array Optional. Defaults to `false`. If `true`, this function will return an array with the following elements: `streamer`, `file`, `url`. For further details, please review this section in your Dashboard: `s2Member → Download Options → JW Player & RTMP Protocol Examples`.
515 *
516 * @return string A File Download URL string on success; or an array on success, with elements `streamer`, `file`, `url` when/if ``$get_streamer_array`` is true; else false on any type of failure.
517 *
518 * @see s2Member\API_Functions\s2member_file_download_url()
519 */
520 public static function create_file_download_url($config = array(), $get_streamer_array = FALSE)
521 {
522 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
523 do_action('ws_plugin__s2member_before_create_file_download_url', get_defined_vars());
524 unset($__refs, $__v); // Housekeeping.
525
526 $config = (is_array($config)) ? $config : array(); // This absolutely MUST be an array.
527
528 $config['file_download'] = (isset($config['file_download']) && is_string($config['file_download'])) ? trim($config['file_download'], '/') : '';
529 $config['file_download_key'] = (!empty($config['file_download_key']) && is_string($config['file_download'])) ? c_ws_plugin__s2member_files::file_download_key($config['file_download'], ((in_array($config['file_download_key'], array('ip-forever', 'universal', 'cache-compatible'))) ? $config['file_download_key'] : FALSE)) : '';
530
531 $config['url_to_storage_source'] = ($get_streamer_array) ? TRUE : @$config['url_to_storage_source']; // Force a streaming URL here via ``$get_streamer_array``?
532 $config['file_stream'] = ($get_streamer_array) ? TRUE : @$config['file_stream']; // Force a streaming URL here via ``$get_streamer_array``?
533
534 if(($url_ = c_ws_plugin__s2member_files_in::check_file_download_access(($config)))) // Successfully created a URL to the file?
535 {
536 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
537 do_action('ws_plugin__s2member_during_create_file_download_url', get_defined_vars());
538 unset($__refs, $__v); // Housekeeping.
539
540 $extension = strtolower(substr($config['file_download'], strrpos($config['file_download'], '.') + 1));
541 $streaming = (isset($config['file_stream'])) ? filter_var($config['file_stream'], FILTER_VALIDATE_BOOLEAN) : ((in_array($extension, preg_split('/['."\r\n\t".'\s;,]+/', $GLOBALS['WS_PLUGIN__']['s2member']['o']['file_download_stream_extensions']))) ? TRUE : FALSE);
542 $ssl = (isset($config['file_ssl'])) ? filter_var($config['file_ssl'], FILTER_VALIDATE_BOOLEAN) : (is_ssl() ? TRUE : FALSE);
543
544 if($get_streamer_array && $streaming && ($cfx = '/cfx/st') && ($cfx_pos = strpos($url_, $cfx)) !== FALSE && ($streamer = substr($url_, 0, $cfx_pos + strlen($cfx))) && ($url = c_ws_plugin__s2member_files_in::check_file_download_access(array_merge($config, array('file_stream' => FALSE, 'check_user' => FALSE, 'count_against_user' => FALSE)))))
545 $return = array('streamer' => $streamer, 'prefix' => $extension.':', 'file' => preg_replace('/^'.preg_quote($streamer, '/').'\//', '', $url_), 'url' => preg_replace('/^.+?\:/', (($ssl) ? 'https:' : 'http:'), $url));
546
547 else if($get_streamer_array && $streaming && is_array($ups = c_ws_plugin__s2member_utils_urls::parse_url($url_)) && isset($ups['scheme'], $ups['host']) && ($streamer = $ups['scheme'].'://'.$ups['host'].((!empty($ups['port'])) ? ':'.$ups['port'] : '')) && ($url = c_ws_plugin__s2member_files_in::check_file_download_access(array_merge($config, array('file_stream' => FALSE, 'check_user' => FALSE, 'count_against_user' => FALSE)))))
548 $return = array('streamer' => $streamer, 'prefix' => '', 'file' => preg_replace('/^'.preg_quote($streamer, '/').'\//', '', $url_), 'url' => preg_replace('/^.+?\:/', (($ssl) ? 'https:' : 'http:'), $url));
549
550 else if($get_streamer_array) // If streamer, we MUST return false here; unable to acquire streamer/file.
551 $return = FALSE; // We MUST return false here, unable to acquire streamer/file.
552
553 else // Else return URL string ( ``$get_streamer_array`` is false ).
554 $return = $url_; // Else return URL string.
555 }
556 return apply_filters('ws_plugin__s2member_create_file_download_url', ((isset($return)) ? $return : FALSE), get_defined_vars());
557 }
558
559 /**
560 * Checks Header Authorization for Remote File Downloads.
561 *
562 * @package s2Member\Files
563 * @since 110926
564 *
565 * @attaches-to ``add_filter('ws_plugin__s2member_check_file_download_access_user');``
566 *
567 * @param WP_User $user Expects a WP_User object passed in by the Filter.
568 *
569 * @return WP_User A `WP_User` object, possibly obtained through Header Authorization.
570 */
571 public static function check_file_remote_authorization($user = NULL)
572 {
573 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
574 do_action('ws_plugin__s2member_before_check_file_remote_authorization', get_defined_vars());
575 unset($__refs, $__v); // Housekeeping.
576
577 $_g = c_ws_plugin__s2member_utils_strings::trim_deep(stripslashes_deep(!empty($_GET) ? $_GET : array()));
578
579 if(!is_object($user) && isset($_g['s2member_file_remote']) && filter_var($_g['s2member_file_remote'], FILTER_VALIDATE_BOOLEAN))
580 {
581 do_action('ws_plugin__s2member_during_check_file_remote_authorization_before', get_defined_vars());
582
583 if((empty($_SERVER['PHP_AUTH_USER']) || $_SERVER['PHP_AUTH_USER'] === 'NOUSER') && !empty($_SERVER['HTTP_AUTHORIZATION']))
584 {
585 $auth = trim(preg_replace('/^.+?\s+/', '', $_SERVER['HTTP_AUTHORIZATION']));
586 $auth = explode(':', base64_decode($auth), 2);
587
588 if(!empty($auth[0])) $_SERVER['PHP_AUTH_USER'] = $auth[0];
589 if(!empty($auth[1])) $_SERVER['PHP_AUTH_PW'] = $auth[1];
590 }
591 if(empty($_SERVER['PHP_AUTH_USER']) || empty($_SERVER['PHP_AUTH_PW']) || !user_pass_ok($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']))
592 {
593 header('WWW-Authenticate: Basic realm="'.c_ws_plugin__s2member_utils_strings::esc_dq(strip_tags(_x('Members Only', 's2member-front', 's2member'))).'"');
594
595 status_header(401); // Send an unauthorized 401 status header now.
596 header('Content-Type: text/html; charset=UTF-8'); // Content-Type with UTF-8.
597 while(@ob_end_clean()) ; // Clean any existing output buffers.
598
599 exit(_x('<strong>401:</strong> Sorry, access denied.', 's2member-front', 's2member'));
600 }
601 else if(is_object($_user = new WP_User($_SERVER['PHP_AUTH_USER'])) && !empty($_user->ID))
602 $user = $_user; // Now assign ``$user``.
603
604 do_action('ws_plugin__s2member_during_check_file_remote_authorization_after', get_defined_vars());
605 }
606 return apply_filters('ws_plugin__s2member_check_file_remote_authorization', $user, get_defined_vars());
607 }
608
609 /**
610 * Checks a File Download Key for validity.
611 *
612 * @package s2Member\Files
613 * @since 110926
614 *
615 * @param string $file Input File Download to validate.
616 * @param string $key Input File Download Key to validate.
617 *
618 * @return bool True if valid, else false.
619 */
620 public static function check_file_download_key($file = '', $key = '')
621 {
622 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
623 do_action('_ws_plugin__s2member_before_check_file_download_key', get_defined_vars());
624 unset($__refs, $__v); // Housekeeping.
625
626 if($file && is_string($file) && ($file = trim($file, '/')) && $key && is_string($key))
627 {
628 if($key === c_ws_plugin__s2member_files::file_download_key($file) || $key === c_ws_plugin__s2member_files::file_download_key('/'.$file))
629 $valid = TRUE; // File Download Key is valid.
630
631 else if($key === c_ws_plugin__s2member_files::file_download_key($file, 'ip-forever') || $key === c_ws_plugin__s2member_files::file_download_key('/'.$file, 'ip-forever'))
632 $valid = TRUE; // File Download Key is valid.
633
634 else if($key === c_ws_plugin__s2member_files::file_download_key($file, 'universal') || $key === c_ws_plugin__s2member_files::file_download_key('/'.$file, 'universal'))
635 $valid = TRUE; // File Download Key is valid.
636 }
637 return apply_filters('ws_plugin__s2member_check_file_download_key', ((isset($valid) && $valid) ? TRUE : FALSE), get_defined_vars());
638 }
639
640 /**
641 * Creates an Amazon S3 HMAC-SHA1 signature.
642 *
643 * @package s2Member\Files
644 * @since 110524RC
645 *
646 * @param string $string Input string/data, to be signed by this routine.
647 *
648 * @return string An HMAC-SHA1 signature for Amazon S3.
649 */
650 public static function amazon_s3_sign($string = '')
651 {
652 $s3c['secret_key'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_secret_key'];
653
654 return c_ws_plugin__s2member_utils_strings::hmac_sha1_sign((string)$string, $s3c['secret_key']);
655 }
656
657 /**
658 * Creates an Amazon S3 AWS4-HMAC-SHA256 signature.
659 *
660 * @package s2Member\Files
661 * @since 150108
662 *
663 * @param string $string Input string/data, to be signed by this routine.
664 *
665 * @return string An AWS4-HMAC-SHA256 signature for Amazon S3.
666 */
667 public static function amazon_s34_sign($string = '')
668 {
669 $s3c = array(); // Initialize config. keys.
670 foreach($GLOBALS['WS_PLUGIN__']['s2member']['o'] as $option => $option_value)
671 if(preg_match('/^amazon_s3_files_/', $option) && ($option = preg_replace('/^amazon_s3_files_/', '', $option)))
672 $s3c[$option] = $option_value;
673
674 $s3_date_key = c_ws_plugin__s2member_utils_strings::hmac_sha256_sign(gmdate('Ymd'), 'AWS4'.$s3c['secret_key'], TRUE);
675 $s3_date_region_key = c_ws_plugin__s2member_utils_strings::hmac_sha256_sign($s3c['bucket_region'], $s3_date_key, TRUE);
676 $s3_date_region_service_key = c_ws_plugin__s2member_utils_strings::hmac_sha256_sign('s3', $s3_date_region_key, TRUE);
677 $s3_signing_key = c_ws_plugin__s2member_utils_strings::hmac_sha256_sign('aws4_request', $s3_date_region_service_key, TRUE);
678
679 return c_ws_plugin__s2member_utils_strings::hmac_sha256_sign((string)$string, $s3_signing_key);
680 }
681
682 /**
683 * Creates an Amazon S3 AWS4-HMAC-SHA256 signature/authorization header.
684 *
685 * @package s2Member\Files
686 * @since 150108
687 *
688 * @param string $s3_date The date header; e.g., `YYYYMMDD'T'HHMMSS'Z'`.
689 * @param string $s3_domain The API endpoint domain; e.g., `[bucket].s3.amazonaws.com`.
690 * @param string $s3_location The API endpoint URI; e.g., `/?acl`.
691 * @param string $s3_method The request method; e.g., `GET`, `PUT`, `POST`, etc.
692 * @param array $s3_headers An associative array of all headers.
693 * @param string $s3_body Any input data sent with the request.
694 * @param boolean $sig_only Return signature only?
695 *
696 * @return string An AWS4-HMAC-SHA256 signature/authorization header for Amazon S3.
697 */
698 public static function amazon_s34_authorization($s3_date = '',
699 $s3_domain = 's3.amazonaws.com',
700 $s3_location = '/', $s3_method = 'GET',
701 $s3_headers = array(), $s3_body = '', $sig_only = FALSE)
702 {
703 $s3_date = trim((string)$s3_date);
704 $s3_domain = trim(strtolower((string)$s3_domain));
705 $s3_location = trim((string)$s3_location);
706 $s3_method = trim(strtoupper((string)$s3_method));
707 $s3_headers = (array)$s3_headers;
708 $s3_body = trim((string)$s3_body);
709
710 $s3c = array(); // Initialize config. keys.
711 foreach($GLOBALS['WS_PLUGIN__']['s2member']['o'] as $option => $option_value)
712 if(preg_match('/^amazon_s3_files_/', $option) && ($option = preg_replace('/^amazon_s3_files_/', '', $option)))
713 $s3c[$option] = $option_value;
714
715 $s3_iso8601_date = gmdate('Ymd\THis\Z');
716 $s3_location_parts = parse_url($s3_location);
717 $s3_canonical_path = !empty($s3_location_parts['path']) ? '/'.ltrim($s3_location_parts['path'], '/') : '/';
718 $s3_scope = gmdate('Ymd').'/'.$s3c['bucket_region'].'/s3/aws4_request';
719
720 $s3_canonical_query = ''; // Initialize.
721 wp_parse_str((string)@$s3_location_parts['query'], $query_args);
722 ksort($query_args, SORT_STRING);
723
724 foreach($query_args as $_key => $_value)
725 $s3_canonical_query .= '&'.c_ws_plugin__s2member_utils_strings::urldecode_ur_chars_deep(rawurlencode($_key)).
726 '='.c_ws_plugin__s2member_utils_strings::urldecode_ur_chars_deep(rawurlencode($_value));
727 $s3_canonical_query = ltrim($s3_canonical_query, '&');
728 unset($_key, $_value); // Housekeeping.
729
730 $s3_canonical_headers = '';
731 $s3_canonical_header_keys = array();
732 ksort($s3_headers, SORT_STRING);
733
734 foreach($s3_headers as $_key => $_value)
735 if(is_string($_key) && ($_key = strtolower($_key)))
736 if(in_array($_key, array('host', 'content-type'), TRUE) || stripos($_key, 'X-Amz-') === 0)
737 {
738 $s3_canonical_headers .= strtolower($_key).':'.trim($_value)."\n";
739 $s3_canonical_header_keys[] = strtolower($_key);
740 }
741 unset($_key, $_value); // Housekeeping.
742
743 $s3_canonicial_request = $s3_method."\n".
744 $s3_canonical_path."\n".
745 $s3_canonical_query."\n".
746 $s3_canonical_headers."\n".
747 implode(';', $s3_canonical_header_keys)."\n".
748 ($s3_body === 'UNSIGNED-PAYLOAD' ? $s3_body : hash('sha256', $s3_body));
749 $s3_string_to_sign = 'AWS4-HMAC-SHA256'."\n".
750 $s3_date."\n".
751 $s3_scope."\n".
752 hash('sha256', $s3_canonicial_request);
753 $s3_signature = self::amazon_s34_sign($s3_string_to_sign);
754
755 // header('Content-Type: text/plain; charset=UTF-8');
756 // echo $s3_canonicial_request."\n\n".$s3_string_to_sign."\n\n"; exit;
757
758 $s3_authorization_header_signature = 'AWS4-HMAC-SHA256 Credential='.$s3c['access_key'].'/'.$s3_scope.','.
759 'SignedHeaders='.implode(';', $s3_canonical_header_keys).','.
760 'Signature='.$s3_signature;
761
762 return $sig_only ? $s3_signature : $s3_authorization_header_signature;
763 }
764
765 /**
766 * Creates an Amazon S3 HMAC-SHA1 signature URL.
767 *
768 * @package s2Member\Files
769 * @since 110926
770 *
771 * @param string $file Input file path, to be signed by this routine.
772 * @param bool $stream Is this resource file to be served as streaming media?
773 * @param bool $inline Is this resource file to be served inline, or no?
774 * @param bool $ssl Is this resource file to be served via SSL, or no?
775 * @param string $basename The absolute basename of the resource file.
776 * @param string $mimetype The MIME content-type of the resource file.
777 *
778 * @return string An HMAC-SHA1 signature URL for Amazon S3.
779 */
780 public static function amazon_s3_url($file = '', $stream = FALSE, $inline = FALSE, $ssl = FALSE, $basename = '', $mimetype = '')
781 {
782 $file = trim((string)$file, '/'); // Trim / force string.
783 $url_e_file = c_ws_plugin__s2member_utils_strings::urldecode_ur_chars_deep(urlencode($file));
784 $url_e_file = str_ireplace('%2F', '/', $url_e_file);
785
786 foreach($GLOBALS['WS_PLUGIN__']['s2member']['o'] as $option => $option_value)
787 if(preg_match('/^amazon_s3_files_/', $option) && ($option = preg_replace('/^amazon_s3_files_/', '', $option)))
788 $s3c[$option] = $option_value;
789
790 $s3c['expires'] = strtotime('+'.apply_filters('ws_plugin__s2member_amazon_s3_file_expires_time', '24 hours', get_defined_vars()));
791
792 $s3_file = add_query_arg(c_ws_plugin__s2member_utils_strings::urldecode_ur_chars_deep(urlencode_deep(array('response-cache-control' => ($s3_cache_control = 'no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0'), 'response-content-disposition' => ($s3_content_disposition = (((bool)$inline) ? 'inline' : 'attachment').'; filename="'.(string)$basename.'"'), 'response-content-type' => ($s3_content_type = (string)$mimetype), 'response-expires' => ($s3_expires = gmdate('D, d M Y H:i:s', strtotime('-1 week')).' GMT')))), '/'.$url_e_file);
793 $s3_raw_file = add_query_arg(array('response-cache-control' => $s3_cache_control, 'response-content-disposition' => $s3_content_disposition, 'response-content-type' => $s3_content_type, 'response-expires' => $s3_expires), '/'.$url_e_file);
794 $s3_signature = base64_encode(c_ws_plugin__s2member_files_in::amazon_s3_sign('GET'."\n\n\n".$s3c['expires']."\n".'/'.$s3c['bucket'].$s3_raw_file));
795
796 $s3_url = ((strtolower($s3c['bucket']) !== $s3c['bucket'])) ? 'http'.(($ssl) ? 's' : '').'://s3.amazonaws.com/'.$s3c['bucket'].$s3_file : 'http'.(($ssl) ? 's' : '').'://'.$s3c['bucket'].'.s3.amazonaws.com'.$s3_file;
797
798 return add_query_arg(c_ws_plugin__s2member_utils_strings::urldecode_ur_chars_deep(urlencode_deep(array('AWSAccessKeyId' => $s3c['access_key'], 'Expires' => $s3c['expires'], 'Signature' => $s3_signature))), $s3_url);
799 }
800
801 /**
802 * Creates an Amazon S3 AWS4-HMAC-SHA256 signature URL.
803 *
804 * @package s2Member\Files
805 * @since 150122
806 *
807 * @param string $file Input file path, to be signed by this routine.
808 * @param bool $stream Is this resource file to be served as streaming media?
809 * @param bool $inline Is this resource file to be served inline, or no?
810 * @param bool $ssl Is this resource file to be served via SSL, or no?
811 * @param string $basename The absolute basename of the resource file.
812 * @param string $mimetype The MIME content-type of the resource file.
813 *
814 * @return string An AWS4-HMAC-SHA256 signature URL for Amazon S3.
815 */
816 public static function amazon_s34_url($file = '', $stream = FALSE, $inline = FALSE, $ssl = FALSE, $basename = '', $mimetype = '')
817 {
818 $file = trim((string)$file, '/'); // Trim / force string.
819 $url_e_file = c_ws_plugin__s2member_utils_strings::urldecode_ur_chars_deep(urlencode($file));
820 $url_e_file = str_ireplace('%2F', '/', $url_e_file);
821
822 $s3c = array(); // Initialize config. keys.
823 foreach($GLOBALS['WS_PLUGIN__']['s2member']['o'] as $option => $option_value)
824 if(preg_match('/^amazon_s3_files_/', $option) && ($option = preg_replace('/^amazon_s3_files_/', '', $option)))
825 $s3c[$option] = $option_value;
826
827 if(!$s3c['bucket_region']) // No region configured; not possible.
828 return self::amazon_s3_url($file, $stream, $inline, $ssl, $basename, $mimetype);
829
830 $s3_date_ymd = date('Ymd');
831 $s3_iso8601_date = gmdate('Ymd\THis\Z');
832 $s3_date = gmdate('D, d M Y H:i:s').' GMT';
833 $s3_algo = 'AWS4-HMAC-SHA256'; // AWS v4 authentication.
834 $s3_credential = $s3c['access_key'].'/'.$s3_date_ymd.'/'.$s3c['bucket_region'].'/s3/aws4_request';
835 $s3_expires = strtotime('+'.apply_filters('ws_plugin__s2member_amazon_s3_file_expires_time', '24 hours', get_defined_vars())) - time();
836 $s3_domain = strtolower($s3c['bucket']) !== $s3c['bucket'] ? 's3.amazonaws.com' : $s3c['bucket'].'.s3.amazonaws.com';
837
838 $s3_args = array('X-Amz-Algorithm' => $s3_algo, 'X-Amz-Credential' => $s3_credential, 'X-Amz-Date' => $s3_iso8601_date, 'X-Amz-Expires' => $s3_expires, 'X-Amz-SignedHeaders' => 'host');
839 $s3_location = add_query_arg(c_ws_plugin__s2member_utils_strings::urldecode_ur_chars_deep(urlencode_deep(array_merge($s3_args, array('response-cache-control' => 'no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0', 'response-content-disposition' => ((bool)$inline ? 'inline' : 'attachment').'; filename="'.(string)$basename.'"', 'response-content-type' => (string)$mimetype, 'response-expires' => gmdate('D, d M Y H:i:s', strtotime('-1 week')).' GMT')))), '/'.$url_e_file);
840 $s3_url = strtolower($s3c['bucket']) !== $s3c['bucket'] ? 'http'.($ssl ? 's' : '').'://s3.amazonaws.com/'.$s3c['bucket'].$s3_location : 'http'.($ssl ? 's' : '').'://'.$s3c['bucket'].'.s3.amazonaws.com'.$s3_location;
841 $s3_sig = self::amazon_s34_authorization($s3_iso8601_date, $s3_domain, $s3_location, 'GET', array('Host' => $s3_domain), 'UNSIGNED-PAYLOAD', TRUE);
842
843 return add_query_arg(c_ws_plugin__s2member_utils_strings::urldecode_ur_chars_deep(urlencode_deep(array('X-Amz-Signature' => $s3_sig))), $s3_url);
844 }
845
846 /**
847 * Auto-configures an Amazon S3 Bucket's ACLs.
848 *
849 * @package s2Member\Files
850 * @since 110926
851 *
852 * @return array Array containing a true `success` element on success, else a failure array.
853 * Failure array will contain a failure `code`, and a failure `message`.
854 */
855 public static function amazon_s3_auto_configure_acls()
856 {
857 foreach($GLOBALS['WS_PLUGIN__']['s2member']['o'] as $option => $option_value)
858 if(preg_match('/^amazon_s3_files_/', $option) && ($option = preg_replace('/^amazon_s3_files_/', '', $option)))
859 $s3c[$option] = $option_value;
860
861 $cfc['distros_s3_access_id'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_cf_files_distros_s3_access_id'];
862
863 if(!empty($s3c) && $s3c['bucket'] && $s3c['access_key'] && $s3c['secret_key']) // Must have Amazon S3 Bucket/Keys.
864 {
865 $s3_iso8601_date = gmdate('Ymd\THis\Z');
866 $s3_date = gmdate('D, d M Y H:i:s').' GMT';
867 $s3_location = strtolower($s3c['bucket']) !== $s3c['bucket'] ? '/'.$s3c['bucket'].'/?acl' : '/?acl';
868 $s3_domain = strtolower($s3c['bucket']) !== $s3c['bucket'] ? 's3.amazonaws.com' : $s3c['bucket'].'.s3.amazonaws.com';
869 $s3_headers = array('Host' => $s3_domain, 'Date' => $s3_date, 'x-amz-date' => $s3_iso8601_date, 'x-amz-content-sha256' => hash('sha256', ''));
870 $s3_headers['Authorization'] = self::amazon_s34_authorization($s3_iso8601_date, $s3_domain, $s3_location, 'GET', $s3_headers, '');
871 $s3_args = array('method' => 'GET', 'redirection' => 5, 'headers' => $s3_headers);
872
873 if(($s3_response = c_ws_plugin__s2member_utils_urls::remote('https://'.$s3_domain.$s3_location, FALSE, array_merge($s3_args, array('timeout' => 20)), 'array')) && $s3_response['code'] === 200)
874 {
875 if(preg_match('/\<Owner\>(.+?)\<\/Owner\>/is', $s3_response['body'], $s3_owner_tag) && preg_match('/\<ID\>(.+?)\<\/ID\>/is', $s3_owner_tag[1], $s3_owner_id_tag) && (preg_match('/\<DisplayName\>(.*?)\<\/DisplayName\>/is', $s3_owner_tag[1], $s3_owner_display_name_tag) || ($s3_owner_display_name_tag = array('-', 'Owner'))))
876 {
877 $s3_owner = array('access_id' => trim($s3_owner_id_tag[1]), 'display_name' => trim($s3_owner_display_name_tag[1]));
878 $s3_acls_xml = '<AccessControlPolicy><Owner><ID>'.esc_html($s3_owner['access_id']).'</ID><DisplayName>'.esc_html($s3_owner['display_name']).'</DisplayName></Owner><AccessControlList><Grant><Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="CanonicalUser"><ID>'.esc_html($s3_owner['access_id']).'</ID><DisplayName>'.esc_html($s3_owner['display_name']).'</DisplayName></Grantee><Permission>FULL_CONTROL</Permission></Grant>'.(($cfc['distros_s3_access_id']) ? '<Grant><Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="CanonicalUser"><ID>'.esc_html($cfc['distros_s3_access_id']).'</ID><DisplayName>s2Member/CloudFront</DisplayName></Grantee><Permission>READ</Permission></Grant>' : '').'</AccessControlList></AccessControlPolicy>';
879 $s3_headers = array('Host' => $s3_domain, 'Date' => $s3_date, 'x-amz-date' => $s3_iso8601_date, 'Content-Type' => 'application/xml', 'x-amz-content-sha256' => hash('sha256', $s3_acls_xml));
880 $s3_headers['Authorization'] = self::amazon_s34_authorization($s3_iso8601_date, $s3_domain, $s3_location, 'PUT', $s3_headers, $s3_acls_xml);
881 $s3_args = array('method' => 'PUT', 'redirection' => 5, 'body' => $s3_acls_xml, 'headers' => $s3_headers);
882
883 if(($s3_response = c_ws_plugin__s2member_utils_urls::remote('https://'.$s3_domain.$s3_location, FALSE, array_merge($s3_args, array('timeout' => 20)), 'array')) && $s3_response['code'] === 200)
884 {
885 $s3_policy_id = md5(uniqid('s2Member/CloudFront:', TRUE));
886 $s3_policy_sid = md5(uniqid('s2Member/CloudFront:', TRUE));
887 $s3_location = strtolower($s3c['bucket']) !== $s3c['bucket'] ? '/'.$s3c['bucket'].'/?policy' : '/?policy';
888 $s3_policy_json = '{"Version":"2008-10-17","Id":"'.c_ws_plugin__s2member_utils_strings::esc_dq($s3_policy_id).'","Statement":[{"Sid":"'.c_ws_plugin__s2member_utils_strings::esc_dq($s3_policy_sid).'","Effect":"Allow","Principal":{"CanonicalUser":"'.c_ws_plugin__s2member_utils_strings::esc_dq($cfc['distros_s3_access_id']).'"},"Action":"s3:GetObject","Resource":"arn:aws:s3:::'.c_ws_plugin__s2member_utils_strings::esc_dq($s3c['bucket']).'/*"}]}';
889 $s3_headers = array('Host' => $s3_domain, 'Date' => $s3_date, 'x-amz-date' => $s3_iso8601_date, 'Content-Type' => 'application/json', 'x-amz-content-sha256' => hash('sha256', $s3_policy_json));
890 $s3_headers['Authorization'] = self::amazon_s34_authorization($s3_iso8601_date, $s3_domain, $s3_location, 'PUT', $s3_headers, $s3_policy_json);
891 $s3_args = array('method' => 'PUT', 'redirection' => 5, 'body' => $s3_policy_json, 'headers' => $s3_headers);
892
893 if(!$cfc['distros_s3_access_id'] || (($s3_response = c_ws_plugin__s2member_utils_urls::remote('https://'.$s3_domain.$s3_location, FALSE, array_merge($s3_args, array('timeout' => 20)), 'array')) && ($s3_response['code'] === 200 || $s3_response['code'] === 204)))
894 {
895 $s3_location = strtolower($s3c['bucket']) !== $s3c['bucket'] ? '/'.$s3c['bucket'].'/crossdomain.xml' : '/crossdomain.xml';
896 $s3_policy_xml = trim(c_ws_plugin__s2member_utilities::evl(file_get_contents(dirname(dirname(__FILE__)).'/templates/cfg-files/s2-cross-xml.php')));
897 $s3_headers = array('Host' => $s3_domain, 'Date' => $s3_date, 'x-amz-date' => $s3_iso8601_date, 'Content-Type' => 'text/xml', 'X-Amz-Acl' => 'public-read', 'x-amz-content-sha256' => hash('sha256', $s3_policy_xml));
898 $s3_headers['Authorization'] = self::amazon_s34_authorization($s3_iso8601_date, $s3_domain, $s3_location, 'PUT', $s3_headers, $s3_policy_xml);
899 $s3_args = array('method' => 'PUT', 'redirection' => 5, 'body' => $s3_policy_xml, 'headers' => $s3_headers);
900
901 if(($s3_response = c_ws_plugin__s2member_utils_urls::remote('https://'.$s3_domain.$s3_location, FALSE, array_merge($s3_args, array('timeout' => 20)), 'array')) && $s3_response['code'] === 200)
902 return array('success' => TRUE, 'code' => NULL, 'message' => NULL); // Successfully configured Amazon S3 Bucket ACLs and Policy.
903
904 else if(isset($s3_response['code'], $s3_response['message']))
905 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon S3 API call. Feel free to exclude `%s` if you like. */
906 return array('success' => FALSE, 'code' => $s3_response['code'], 'message' => sprintf(_x('Unable to update existing Amazon S3 Cross-Domain Policy. %s', 's2member-admin', 's2member'), $s3_response['message']));
907
908 else // Else, we use a default error code and message.
909 return array('success' => FALSE, 'code' => -94, 'message' => _x('Unable to update existing Amazon S3 Cross-Domain Policy. Connection failed.', 's2member-admin', 's2member'));
910 }
911 else if(isset($s3_response['code'], $s3_response['message']))
912 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon S3 API call. Feel free to exclude `%s` if you like. */
913 return array('success' => FALSE, 'code' => $s3_response['code'], 'message' => sprintf(_x('Unable to update existing Amazon S3 Bucket Policy. %s', 's2member-admin', 's2member'), $s3_response['message']));
914
915 else // Else, we use a default error code and message.
916 return array('success' => FALSE, 'code' => -95, 'message' => _x('Unable to update existing Amazon S3 Bucket Policy. Connection failed.', 's2member-admin', 's2member'));
917 }
918 else if(isset($s3_response['code'], $s3_response['message']))
919 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon S3 API call. Feel free to exclude `%s` if you like. */
920 return array('success' => FALSE, 'code' => $s3_response['code'], 'message' => sprintf(_x('Unable to update existing Amazon S3 Bucket ACLs. %s', 's2member-admin', 's2member'), $s3_response['message']));
921
922 else // Else, we use a default error code and message.
923 return array('success' => FALSE, 'code' => -96, 'message' => _x('Unable to update existing Amazon S3 Bucket ACLs. Connection failed.', 's2member-admin', 's2member'));
924 }
925 else // Else, we use a default error code and message.
926 return array('success' => FALSE, 'code' => -97, 'message' => _x('Unable to acquire/read existing Amazon S3 Bucket ACLs. Unexpected response.', 's2member-admin', 's2member'));
927 }
928 else if(isset($s3_response['code'], $s3_response['message']))
929 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon S3 API call. Feel free to exclude `%s` if you like. */
930 return array('success' => FALSE, 'code' => $s3_response['code'], 'message' => sprintf(_x('Unable to acquire existing Amazon S3 Bucket ACLs. %s', 's2member-admin', 's2member'), $s3_response['message']));
931
932 else // Else, we use a default error code and message.
933 return array('success' => FALSE, 'code' => -98, 'message' => _x('Unable to acquire existing Amazon S3 Bucket ACLs. Connection failed.', 's2member-admin', 's2member'));
934 }
935 else // Else, we use a default error code and message.
936 return array('success' => FALSE, 'code' => -99, 'message' => _x('Unable to auto-configure existing Amazon S3 Bucket ACLs. Incomplete Amazon S3 configuration options. Missing one of: Amazon S3 Bucket, Access Key, or Secret Key.', 's2member-admin', 's2member'));
937 }
938
939 /**
940 * Creates an Amazon CloudFront HMAC-SHA1 signature.
941 *
942 * @package s2Member\Files
943 * @since 110926
944 *
945 * @param string $string Input string/data, to be signed by this routine.
946 *
947 * @return string An HMAC-SHA1 signature for Amazon CloudFront.
948 */
949 public static function amazon_cf_sign($string = '')
950 {
951 $cfc['secret_key'] = $s3c['secret_key'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_secret_key'];
952
953 return c_ws_plugin__s2member_utils_strings::hmac_sha1_sign((string)$string, ($cfc['secret_key'] = $s3c['secret_key']));
954 }
955
956 /**
957 * Creates an Amazon CloudFront RSA-SHA1 signature.
958 *
959 * @package s2Member\Files
960 * @since 110926
961 *
962 * @param string $string Input string/data, to be signed by this routine.
963 *
964 * @return string|bool An RSA-SHA1 signature for Amazon CloudFront, else false on failure.
965 */
966 public static function amazon_cf_rsa_sign($string = '')
967 {
968 $cfc['private_key'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_cf_files_private_key'];
969
970 return c_ws_plugin__s2member_utils_strings::rsa_sha1_sign((string)$string, $cfc['private_key']);
971 }
972
973 /**
974 * Creates an Amazon CloudFront RSA-SHA1 signature URL.
975 *
976 * @package s2Member\Files
977 * @since 110926
978 *
979 * @param string $file Input file path, to be signed by this routine.
980 * @param bool $stream Is this resource file to be served as streaming media?
981 * @param bool $inline Is this resource file to be served inline, or no?
982 * @param bool $ssl Is this resource file to be served via SSL, or no?
983 * @param string $basename The absolute basename of the resource file.
984 * @param string $mimetype The MIME content-type of the resource file.
985 *
986 * @return string An RSA-SHA1 signature URL for Amazon CloudFront.
987 */
988 public static function amazon_cf_url($file = '', $stream = FALSE, $inline = FALSE, $ssl = FALSE, $basename = '', $mimetype = '')
989 {
990 $file = trim((string)$file, '/'); // Trim & force string.
991 $url_e_file = c_ws_plugin__s2member_utils_strings::urldecode_ur_chars_deep(urlencode($file));
992 $url_e_file = str_ireplace('%2F', '/', $url_e_file);
993
994 foreach($GLOBALS['WS_PLUGIN__']['s2member']['o'] as $option => $option_value)
995 if(preg_match('/^amazon_cf_files_/', $option) && ($option = preg_replace('/^amazon_cf_files_/', '', $option)))
996 $cfc[$option] = $option_value;
997
998 $cfc['expires'] = strtotime('+'.apply_filters('ws_plugin__s2member_amazon_cf_file_expires_time', '24 hours', get_defined_vars()));
999
1000 $cf_extn = strtolower(substr($file, strrpos($file, '.') + 1));
1001 $cf_ip_res = c_ws_plugin__s2member_utils_conds::is_localhost() || ($stream && !$cfc['rtmp_policy_include_ip']) ? FALSE : TRUE;
1002 $cf_stream_extn_resource_exclusions = array_unique((array)apply_filters('ws_plugin__s2member_amazon_cf_file_streaming_extension_resource_exclusions', array('mp3'), get_defined_vars())); // MP3 files should NOT include an extension in their resource reference.
1003 $cf_resource = ($stream) ? ((in_array($cf_extn, $cf_stream_extn_resource_exclusions)) ? substr($file, 0, strrpos($file, '.')) : $file) : 'http'.(($ssl) ? 's' : '').'://'.(($cfc['distro_downloads_cname']) ? $cfc['distro_downloads_cname'] : $cfc['distro_downloads_dname']).'/'.$url_e_file;
1004 $cf_url = ($stream) ? 'rtmp'.(($ssl) ? 'e' : '').'://'.(($cfc['distro_streaming_cname']) ? $cfc['distro_streaming_cname'] : $cfc['distro_streaming_dname']).'/cfx/st/'.$file : 'http'.(($ssl) ? 's' : '').'://'.(($cfc['distro_downloads_cname']) ? $cfc['distro_downloads_cname'] : $cfc['distro_downloads_dname']).'/'.$url_e_file;
1005 $cf_policy = '{"Statement":[{"Resource":"'.c_ws_plugin__s2member_utils_strings::esc_dq($cf_resource).'","Condition":{'.(($cf_ip_res) ? '"IpAddress":{"AWS:SourceIp":"'.c_ws_plugin__s2member_utils_strings::esc_dq(c_ws_plugin__s2member_utils_ip::current()).'/32"},' : '').'"DateLessThan":{"AWS:EpochTime":'.(int)$cfc['expires'].'}}}]}';
1006
1007 $cf_signature = c_ws_plugin__s2member_files_in::amazon_cf_rsa_sign($cf_policy);
1008 $cf_base64_url_safe_policy = c_ws_plugin__s2member_utils_strings::base64_url_safe_encode($cf_policy, array('+', '=', '/'), array('-', '_', '~'), FALSE);
1009 $cf_base64_url_safe_signature = c_ws_plugin__s2member_utils_strings::base64_url_safe_encode($cf_signature, array('+', '=', '/'), array('-', '_', '~'), FALSE);
1010
1011 return add_query_arg(c_ws_plugin__s2member_utils_strings::urldecode_ur_chars_deep(urlencode_deep(array('Policy' => $cf_base64_url_safe_policy, 'Signature' => $cf_base64_url_safe_signature, 'Key-Pair-Id' => $cfc['private_key_id']))), $cf_url);
1012 }
1013
1014 /**
1015 * Auto-configures Amazon S3/CloudFront distros.
1016 *
1017 * @package s2Member\Files
1018 * @since 110926
1019 *
1020 * @return array Array containing a true `success` element on success, else a failure array.
1021 * Failure array will contain a failure `code`, and a failure `message`.
1022 */
1023 public static function amazon_cf_auto_configure_distros()
1024 {
1025 foreach($GLOBALS['WS_PLUGIN__']['s2member']['o'] as $option => $option_value)
1026 if(preg_match('/^amazon_cf_files_/', $option) && ($option = preg_replace('/^amazon_cf_files_/', '', $option)))
1027 $cfc[$option] = $option_value;
1028
1029 $s3c['bucket'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_bucket'];
1030 $cfc['access_key'] = $s3c['access_key'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_access_key'];
1031 $cfc['secret_key'] = $s3c['secret_key'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_secret_key'];
1032
1033 if($s3c['bucket'] && $s3c['access_key'] && $s3c['secret_key']) // We MUST have an Amazon S3 Bucket and Keys.
1034 {
1035 if($cfc['private_key'] && $cfc['private_key_id']) // We MUST have Amazon CloudFront Keys in order to auto-configure.
1036 {
1037 if(!$cfc['distro_downloads_id'] || ($cfc['distro_downloads_id'] && ($cf_get_response = c_ws_plugin__s2member_files_in::amazon_cf_get_distro($cfc['distro_downloads_id'], 'downloads')) && ($cf_get_response['success'] || $cf_get_response['code'] === 404)))
1038 {
1039 if(!$cfc['distro_downloads_id'] || ($cfc['distro_downloads_id'] && !empty($cf_get_response) && !$cf_get_response['success'] && $cf_get_response['code'] === 404))
1040 $cf_distro_downloads_clear = TRUE; // Clear, ready for a new one.
1041
1042 else if($cfc['distro_downloads_id'] && !empty($cf_get_response) && $cf_get_response['success'] && !$cf_get_response['deployed'])
1043 return array('success' => FALSE, 'code' => -86, 'message' => _x('Unable to delete existing Amazon CloudFront Downloads Distro. Still in a `pending` state. Please wait 15 minutes, then try again. There is a certain process that s2Member must strictly adhere to when re-configuring your Amazon CloudFront Distros. You may have to tick the auto-configure checkbox again, and re-run s2Member\'s auto-configuration routine many times, because s2Member will likely run into several `pending` challenges, as it works to completely re-configure your Amazon CloudFront Distros for you. Thanks for your patience. Please wait 15 minutes, then try again.', 's2member-admin', 's2member'));
1044
1045 else if($cfc['distro_downloads_id'] && !empty($cf_get_response) && $cf_get_response['success'] && $cf_get_response['deployed'] && ($cf_del_response = c_ws_plugin__s2member_files_in::amazon_cf_del_distro($cfc['distro_downloads_id'], $cf_get_response['etag'], $cf_get_response['xml'])) && $cf_del_response['success'])
1046 $cf_distro_downloads_clear = TRUE; // Clear, ready for a new one.
1047
1048 else if(isset($cf_del_response['code'], $cf_del_response['message']))
1049 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon CloudFront API call. Feel free to exclude `%s` if you like. */
1050 return array('success' => FALSE, 'code' => $cf_del_response['code'], 'message' => sprintf(_x('Unable to delete existing Amazon CloudFront Downloads Distro. %s', 's2member-admin', 's2member'), $cf_del_response['message']));
1051
1052 if(isset($cf_distro_downloads_clear) && $cf_distro_downloads_clear) // Successfully cleared? Ready for a new one?
1053 {
1054 unset($cf_get_response, $cf_del_response); // Unset these before processing additional routines. Prevents problems in error reporting.
1055
1056 if(!$cfc['distro_streaming_id'] || ($cfc['distro_streaming_id'] && ($cf_get_response = c_ws_plugin__s2member_files_in::amazon_cf_get_distro($cfc['distro_streaming_id'], 'streaming')) && ($cf_get_response['success'] || $cf_get_response['code'] === 404)))
1057 {
1058 if(!$cfc['distro_streaming_id'] || ($cfc['distro_streaming_id'] && !empty($cf_get_response) && !$cf_get_response['success'] && $cf_get_response['code'] === 404))
1059 $cf_distro_streaming_clear = TRUE; // Clear, ready for a new one.
1060
1061 else if($cfc['distro_streaming_id'] && !empty($cf_get_response) && $cf_get_response['success'] && !$cf_get_response['deployed'])
1062 return array('success' => FALSE, 'code' => -87, 'message' => _x('Unable to delete existing Amazon CloudFront Streaming Distro. Still in a `pending` state. Please wait 15 minutes, then try again. There is a certain process that s2Member must strictly adhere to when re-configuring your Amazon CloudFront Distros. You may have to tick the auto-configure checkbox again, and re-run s2Member\'s auto-configuration routine many times, because s2Member will likely run into several `pending` challenges, as it works to completely re-configure your Amazon CloudFront Distros for you. Thanks for your patience. Please wait 15 minutes, then try again.', 's2member-admin', 's2member'));
1063
1064 else if($cfc['distro_streaming_id'] && !empty($cf_get_response) && $cf_get_response['success'] && $cf_get_response['deployed'] && ($cf_del_response = c_ws_plugin__s2member_files_in::amazon_cf_del_distro($cfc['distro_streaming_id'], $cf_get_response['etag'], $cf_get_response['xml'])) && $cf_del_response['success'])
1065 $cf_distro_streaming_clear = TRUE; // Clear, ready for a new one.
1066
1067 else if(isset($cf_del_response['code'], $cf_del_response['message']))
1068 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon CloudFront API call. Feel free to exclude `%s` if you like. */
1069 return array('success' => FALSE, 'code' => $cf_del_response['code'], 'message' => sprintf(_x('Unable to delete existing Amazon CloudFront Streaming Distro. %s', 's2member-admin', 's2member'), $cf_del_response['message']));
1070
1071 if(isset($cf_distro_streaming_clear) && $cf_distro_streaming_clear) // Successfully cleared? Ready for a new one?
1072 {
1073 unset($cf_get_response, $cf_del_response); // Unset these before processing additional routines. Prevents problems in error reporting.
1074
1075 if(!$cfc['distros_access_id'] || ($cfc['distros_access_id'] && ($cf_get_response = c_ws_plugin__s2member_files_in::amazon_cf_get_access_origin_identity($cfc['distros_access_id'])) && ($cf_get_response['success'] || $cf_get_response['code'] === 404)))
1076 {
1077 if(!$cfc['distros_access_id'] || ($cfc['distros_access_id'] && !empty($cf_get_response) && !$cf_get_response['success'] && $cf_get_response['code'] === 404))
1078 $cf_distros_access_clear = TRUE; // Clear, ready for a new one.
1079
1080 else if($cfc['distros_access_id'] && !empty($cf_get_response) && $cf_get_response['success'] && ($cf_del_response = c_ws_plugin__s2member_files_in::amazon_cf_del_access_origin_identity($cfc['distros_access_id'], $cf_get_response['etag'], $cf_get_response['xml'])) && $cf_del_response['success'])
1081 $cf_distros_access_clear = TRUE; // Clear, ready for a new one.
1082
1083 else if(isset($cf_del_response['code'], $cf_del_response['message']))
1084 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon CloudFront API call. Feel free to exclude `%s` if you like. */
1085 return array('success' => FALSE, 'code' => $cf_del_response['code'], 'message' => sprintf(_x('Unable to delete existing Amazon CloudFront Origin Access Identity. %s', 's2member-admin', 's2member'), $cf_del_response['message']));
1086
1087 if(isset($cf_distros_access_clear) && $cf_distros_access_clear) // Successfully cleared? Ready for a new one?
1088 {
1089 unset($cf_get_response, $cf_del_response); // Unset these before processing additional routines. Prevents problems in error reporting.
1090
1091 $cfc = array_merge($cfc, array('distros_access_id' => '', 'distros_s3_access_id' => '', 'distro_downloads_id' => '', 'distro_downloads_dname' => '', 'distro_streaming_id' => '', 'distro_streaming_dname' => '', 'distros_auto_config_status' => ''));
1092 $cf_options = array('ws_plugin__s2member_amazon_cf_files_distros_access_id' => '', 'ws_plugin__s2member_amazon_cf_files_distros_s3_access_id' => '', 'ws_plugin__s2member_amazon_cf_files_distro_downloads_id' => '', 'ws_plugin__s2member_amazon_cf_files_distro_downloads_dname' => '', 'ws_plugin__s2member_amazon_cf_files_distro_streaming_id' => '', 'ws_plugin__s2member_amazon_cf_files_distro_streaming_dname' => '', 'ws_plugin__s2member_amazon_cf_files_distros_auto_config_status' => '');
1093 c_ws_plugin__s2member_menu_pages::update_all_options($cf_options, TRUE, FALSE, FALSE, FALSE, FALSE);
1094
1095 if(($cf_response = c_ws_plugin__s2member_files_in::amazon_cf_create_distros_access_origin_identity()) && $cf_response['success'])
1096 {
1097 $cfc = array_merge($cfc, array('distros_access_id' => $cf_response['distros_access_id'], 'distros_s3_access_id' => $cf_response['distros_s3_access_id']));
1098 $cf_options = array('ws_plugin__s2member_amazon_cf_files_distros_access_id' => $cf_response['distros_access_id'], 'ws_plugin__s2member_amazon_cf_files_distros_s3_access_id' => $cf_response['distros_s3_access_id']);
1099 c_ws_plugin__s2member_menu_pages::update_all_options($cf_options, TRUE, FALSE, FALSE, FALSE, FALSE);
1100
1101 if(($cf_response = c_ws_plugin__s2member_files_in::amazon_cf_create_distro('downloads')) && $cf_response['success'])
1102 {
1103 $cfc = array_merge($cfc, array('distro_downloads_id' => $cf_response['distro_downloads_id'], 'distro_downloads_dname' => $cf_response['distro_downloads_dname']));
1104 $cf_options = array('ws_plugin__s2member_amazon_cf_files_distro_downloads_id' => $cf_response['distro_downloads_id'], 'ws_plugin__s2member_amazon_cf_files_distro_downloads_dname' => $cf_response['distro_downloads_dname']);
1105 c_ws_plugin__s2member_menu_pages::update_all_options($cf_options, TRUE, FALSE, FALSE, FALSE, FALSE);
1106
1107 if(($cf_response = c_ws_plugin__s2member_files_in::amazon_cf_create_distro('streaming')) && $cf_response['success'])
1108 {
1109 $cfc = array_merge($cfc, array('distro_streaming_id' => $cf_response['distro_streaming_id'], 'distro_streaming_dname' => $cf_response['distro_streaming_dname']));
1110 $cf_options = array('ws_plugin__s2member_amazon_cf_files_distro_streaming_id' => $cf_response['distro_streaming_id'], 'ws_plugin__s2member_amazon_cf_files_distro_streaming_dname' => $cf_response['distro_streaming_dname']);
1111 c_ws_plugin__s2member_menu_pages::update_all_options($cf_options, TRUE, FALSE, FALSE, FALSE, FALSE);
1112
1113 for($a = 1, $attempts = 4, $sleep = 2, sleep($sleep); $a <= $attempts; $a++, (($a <= $attempts) ? sleep($sleep) : NULL))
1114 /* Allow a generous propagation time here. Amazon\'s high-availability services do NOT guarantee real-time updates.
1115 Since we DO need a fully propagated Origin Access Identity now, we need to make several attempts at success.
1116 For further details, please see this thread: <https://forums.aws.amazon.com/message.jspa?messageID=42875>. */
1117 if(($s3_response = c_ws_plugin__s2member_files_in::amazon_s3_auto_configure_acls()) && $s3_response['success'])
1118 {
1119 $cfc = array_merge($cfc, array('distros_auto_config_status' => 'configured'));
1120 $cf_options = array('ws_plugin__s2member_amazon_cf_files_distros_auto_config_status' => 'configured');
1121 c_ws_plugin__s2member_menu_pages::update_all_options($cf_options, TRUE, FALSE, FALSE, FALSE, FALSE); // Now configured!
1122 return array('success' => TRUE, 'code' => NULL, 'message' => NULL); // Successfully configured Amazon S3/CloudFront distros.
1123 }
1124 if(isset($s3_response['code'], $s3_response['message']))
1125 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon S3 API call. Feel free to exclude `%s` if you like. */
1126 return array('success' => FALSE, 'code' => $s3_response['code'], 'message' => sprintf(_x('Unable to update existing Amazon S3 ACLs. %s', 's2member-admin', 's2member'), $s3_response['message']));
1127
1128 else // Else, we use a default error code and message.
1129 return array('success' => FALSE, 'code' => -88, 'message' => _x('Unable to update existing Amazon S3 ACLs. Connection failed.', 's2member-admin', 's2member'));
1130 }
1131 else if(isset($cf_response['code'], $cf_response['message']))
1132 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon CloudFront API call. Feel free to exclude `%s` if you like. */
1133 return array('success' => FALSE, 'code' => $cf_response['code'], 'message' => sprintf(_x('Unable to create Amazon CloudFront Streaming Distro. %s', 's2member-admin', 's2member'), $cf_response['message']));
1134
1135 else // Else, we use a default error code and message.
1136 return array('success' => FALSE, 'code' => -89, 'message' => _x('Unable to create Amazon CloudFront Streaming Distro. Connection failed.', 's2member-admin', 's2member'));
1137 }
1138 else if(isset($cf_response['code'], $cf_response['message']))
1139 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon CloudFront API call. Feel free to exclude `%s` if you like. */
1140 return array('success' => FALSE, 'code' => $cf_response['code'], 'message' => sprintf(_x('Unable to create Amazon CloudFront Downloads Distro. %s', 's2member-admin', 's2member'), $cf_response['message']));
1141
1142 else // Else, we use a default error code and message.
1143 return array('success' => FALSE, 'code' => -90, 'message' => _x('Unable to create Amazon CloudFront Downloads Distro. Connection failed.', 's2member-admin', 's2member'));
1144 }
1145 else if(isset($cf_response['code'], $cf_response['message']))
1146 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon CloudFront API call. Feel free to exclude `%s` if you like. */
1147 return array('success' => FALSE, 'code' => $cf_response['code'], 'message' => sprintf(_x('Unable to create Amazon CloudFront Origin Access Identity. %s', 's2member-admin', 's2member'), $cf_response['message']));
1148
1149 else // Else, we use a default error code and message.
1150 return array('success' => FALSE, 'code' => -91, 'message' => _x('Unable to create Amazon CloudFront Origin Access Identity. Connection failed.', 's2member-admin', 's2member'));
1151 }
1152 else // Else, we use a default error code and message.
1153 return array('success' => FALSE, 'code' => -92, 'message' => _x('Unable to clear existing Amazon CloudFront Origin Access Identity.', 's2member-admin', 's2member'));
1154 }
1155 else if(isset($cf_get_response['code'], $cf_get_response['message']))
1156 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon CloudFront API call. Feel free to exclude `%s` if you like. */
1157 return array('success' => FALSE, 'code' => $cf_get_response['code'], 'message' => sprintf(_x('Unable to acquire existing Amazon CloudFront Origin Access Identity. %s', 's2member-admin', 's2member'), $cf_get_response['message']));
1158
1159 else // Else, we use a default error code and message.
1160 return array('success' => FALSE, 'code' => -93, 'message' => _x('Unable to acquire existing Amazon CloudFront Origin Access Identity. Connection failed.', 's2member-admin', 's2member'));
1161 }
1162 else // Else, we use a default error code and message.
1163 return array('success' => FALSE, 'code' => -94, 'message' => _x('Unable to clear existing Amazon CloudFront Streaming Distro.', 's2member-admin', 's2member'));
1164 }
1165 else if(isset($cf_get_response['code'], $cf_get_response['message']))
1166 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon CloudFront API call. Feel free to exclude `%s` if you like. */
1167 return array('success' => FALSE, 'code' => $cf_get_response['code'], 'message' => sprintf(_x('Unable to acquire existing Amazon CloudFront Streaming Distro. %s', 's2member-admin', 's2member'), $cf_get_response['message']));
1168
1169 else // Else, we use a default error code and message.
1170 return array('success' => FALSE, 'code' => -95, 'message' => _x('Unable to acquire existing Amazon CloudFront Streaming Distro. Connection failed.', 's2member-admin', 's2member'));
1171 }
1172 else // Else, we use a default error code and message.
1173 return array('success' => FALSE, 'code' => -96, 'message' => _x('Unable to clear existing Amazon CloudFront Downloads Distro.', 's2member-admin', 's2member'));
1174 }
1175 else if(isset($cf_get_response['code'], $cf_get_response['message']))
1176 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon CloudFront API call. Feel free to exclude `%s` if you like. */
1177 return array('success' => FALSE, 'code' => $cf_get_response['code'], 'message' => sprintf(_x('Unable to acquire existing Amazon CloudFront Downloads Distro. %s', 's2member-admin', 's2member'), $cf_get_response['message']));
1178
1179 else // Else, we use a default error code and message.
1180 return array('success' => FALSE, 'code' => -97, 'message' => _x('Unable to acquire existing Amazon CloudFront Downloads Distro. Connection failed.', 's2member-admin', 's2member'));
1181 }
1182 else // Else, we use a default error code and message.
1183 return array('success' => FALSE, 'code' => -98, 'message' => _x('Unable to auto-configure Amazon CloudFront Distros. Incomplete Amazon CloudFront configuration options. Missing of one: Amazon CloudFront Private Key-Pair-ID, or Private Key file contents.', 's2member-admin', 's2member'));
1184 }
1185 else // Else, we use a default error code and message.
1186 return array('success' => FALSE, 'code' => -99, 'message' => _x('Unable to auto-configure Amazon S3/CloudFront Distros. Incomplete Amazon S3 configuration options. Missing one of: Amazon S3 Bucket, Access Key, or Secret Key. You must provide s2Member with an Amazon S3 configuration before enabling CloudFront.', 's2member-admin', 's2member'));
1187 }
1188
1189 /**
1190 * Acquires an Amazon S3/CloudFront Access Origin Identity.
1191 *
1192 * @package s2Member\Files
1193 * @since 110926
1194 *
1195 * @param string $access_id Required. An Origin Access ID.
1196 *
1197 * @return array Array containing a true `success` and `etag`, `xml` elements on success, else a failure array.
1198 * Failure array will contain a failure `code`, and a failure `message`.
1199 */
1200 public static function amazon_cf_get_access_origin_identity($access_id = '')
1201 {
1202 if($access_id && is_string($access_id)) // Valid parameters?
1203 {
1204 foreach($GLOBALS['WS_PLUGIN__']['s2member']['o'] as $option => $option_value)
1205 if(preg_match('/^amazon_cf_files_/', $option) && ($option = preg_replace('/^amazon_cf_files_/', '', $option)))
1206 $cfc[$option] = $option_value;
1207
1208 $s3c['bucket'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_bucket'];
1209 $cfc['access_key'] = $s3c['access_key'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_access_key'];
1210 $cfc['secret_key'] = $s3c['secret_key'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_secret_key'];
1211
1212 $cf_domain = 'cloudfront.amazonaws.com';
1213 $cf_date = gmdate('D, d M Y H:i:s').' GMT';
1214 $cf_location = '/2010-11-01/origin-access-identity/cloudfront/'.$access_id;
1215 $cf_signature = base64_encode(c_ws_plugin__s2member_files_in::amazon_cf_sign($cf_date));
1216 $cf_args = array('method' => 'GET', 'redirection' => 5, 'headers' => array('Host' => $cf_domain, 'Date' => $cf_date, 'Authorization' => 'AWS '.$cfc['access_key'].':'.$cf_signature));
1217
1218 if(($cf_response = c_ws_plugin__s2member_utils_urls::remote('https://'.$cf_domain.$cf_location, FALSE, array_merge($cf_args, array('timeout' => 20)), 'array')) && (($cf_response['code'] === 404 && $cf_response['message']) || ($cf_response['code'] === 200 && !empty($cf_response['headers']['etag']) && !empty($cf_response['body']))))
1219 {
1220 if($cf_response['code'] === 200 && !empty($cf_response['headers']['etag']) && !empty($cf_response['body']))
1221 return array('success' => TRUE, 'code' => NULL, 'message' => NULL, 'etag' => trim($cf_response['headers']['etag']), 'xml' => trim($cf_response['body']));
1222
1223 else /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon CloudFront API call. Feel free to exclude `%s` if you like. */
1224 return array('success' => FALSE, 'code' => $cf_response['code'], 'message' => sprintf(_x('Existing Amazon CloudFront Origin Access Identity NOT found. %s', 's2member-admin', 's2member'), $cf_response['message']));
1225 }
1226 else if(isset($cf_response['code'], $cf_response['message']))
1227 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon CloudFront API call. Feel free to exclude `%s` if you like. */
1228 return array('success' => FALSE, 'code' => $cf_response['code'], 'message' => sprintf(_x('Unable to acquire existing Amazon CloudFront Origin Access Identity. %s', 's2member-admin', 's2member'), $cf_response['message']));
1229
1230 else // Else, we use a default error code and message.
1231 return array('success' => FALSE, 'code' => -98, 'message' => _x('Unable to acquire existing Amazon CloudFront Origin Access Identity. Connection failed.', 's2member-admin', 's2member'));
1232 }
1233 else // Else, we use a default error code and message.
1234 return array('success' => FALSE, 'code' => -99, 'message' => _x('Unable to acquire existing Amazon CloudFront Origin Access Identity. Invalid Access ID.', 's2member-admin', 's2member'));
1235 }
1236
1237 /**
1238 * Deletes an Amazon S3/CloudFront Access Origin Identity.
1239 *
1240 * @package s2Member\Files
1241 * @since 110926
1242 *
1243 * @param string $access_id Required. An Origin Access ID.
1244 * @param string $access_id_etag Required. An Origin Access ETag header.
1245 * @param string $access_id_xml Required. An Origin Access Identity's XML configuration.
1246 *
1247 * @return array Array containing a true `success` element on success, else a failure array.
1248 * Failure array will contain a failure `code`, and a failure `message`.
1249 */
1250 public static function amazon_cf_del_access_origin_identity($access_id = '', $access_id_etag = '', $access_id_xml = '')
1251 {
1252 if($access_id && is_string($access_id) && $access_id_etag && is_string($access_id_etag) && $access_id_xml && is_string($access_id_xml))
1253 {
1254 foreach($GLOBALS['WS_PLUGIN__']['s2member']['o'] as $option => $option_value)
1255 if(preg_match('/^amazon_cf_files_/', $option) && ($option = preg_replace('/^amazon_cf_files_/', '', $option)))
1256 $cfc[$option] = $option_value;
1257
1258 $s3c['bucket'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_bucket'];
1259 $cfc['access_key'] = $s3c['access_key'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_access_key'];
1260 $cfc['secret_key'] = $s3c['secret_key'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_secret_key'];
1261
1262 $cf_domain = 'cloudfront.amazonaws.com';
1263 $cf_date = gmdate('D, d M Y H:i:s').' GMT';
1264 $cf_location = '/2010-11-01/origin-access-identity/cloudfront/'.$access_id;
1265 $cf_signature = base64_encode(c_ws_plugin__s2member_files_in::amazon_cf_sign($cf_date));
1266 $cf_args = array('method' => 'DELETE', 'redirection' => 5, 'headers' => array('Host' => $cf_domain, 'Date' => $cf_date, 'If-Match' => $access_id_etag, 'Authorization' => 'AWS '.$cfc['access_key'].':'.$cf_signature));
1267
1268 if(($cf_response = c_ws_plugin__s2member_utils_urls::remote('https://'.$cf_domain.$cf_location, FALSE, array_merge($cf_args, array('timeout' => 20)), 'array')) && ($cf_response['code'] === 200 || $cf_response['code'] === 204))
1269 return array('success' => TRUE, 'code' => NULL, 'message' => NULL); // Deleted successfully.
1270
1271 else if(isset($cf_response['code'], $cf_response['message']))
1272 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon CloudFront API call. Feel free to exclude `%s` if you like. */
1273 return array('success' => FALSE, 'code' => $cf_response['code'], 'message' => sprintf(_x('Unable to delete existing Amazon CloudFront Origin Access Identity. %s', 's2member-admin', 's2member'), $cf_response['message']));
1274
1275 else // Else, we use a default error code and message.
1276 return array('success' => FALSE, 'code' => -98, 'message' => _x('Unable to delete existing Amazon CloudFront Origin Access Identity. Connection failed.', 's2member-admin', 's2member'));
1277 }
1278 else // Else, we use a default error code and message.
1279 return array('success' => FALSE, 'code' => -99, 'message' => _x('Unable to delete existing Amazon CloudFront Origin Access Identity. Invalid Access ID, ETag, or XML config.', 's2member-admin', 's2member'));
1280 }
1281
1282 /**
1283 * Creates an Amazon S3/CloudFront Access Origin Identity for all Distros.
1284 *
1285 * @package s2Member\Files
1286 * @since 110926
1287 *
1288 * @return array Array containing a true `success` and `distros_access_id`, `distros_s3_access_id` elements on success, else a failure array.
1289 * Failure array will contain a failure `code`, and a failure `message`.
1290 */
1291 public static function amazon_cf_create_distros_access_origin_identity()
1292 {
1293 foreach($GLOBALS['WS_PLUGIN__']['s2member']['o'] as $option => $option_value)
1294 if(preg_match('/^amazon_cf_files_/', $option) && ($option = preg_replace('/^amazon_cf_files_/', '', $option)))
1295 $cfc[$option] = $option_value;
1296
1297 $s3c['bucket'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_bucket'];
1298 $cfc['access_key'] = $s3c['access_key'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_access_key'];
1299 $cfc['secret_key'] = $s3c['secret_key'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_secret_key'];
1300
1301 $cf_domain = 'cloudfront.amazonaws.com';
1302 $cf_date = gmdate('D, d M Y H:i:s').' GMT';
1303 $cf_location = '/2010-11-01/origin-access-identity/cloudfront';
1304 $cf_signature = base64_encode(c_ws_plugin__s2member_files_in::amazon_cf_sign($cf_date));
1305 $cf_distros_access_reference = time().'.'.md5('access'.$s3c['bucket'].$s3c['access_key'].$s3c['secret_key'].$cfc['private_key'].$cfc['private_key_id']);
1306 $cf_distros_access_xml = '<?xml version="1.0" encoding="UTF-8"?><CloudFrontOriginAccessIdentityConfig xmlns="http://cloudfront.amazonaws.com/doc/2010-11-01/"><CallerReference>'.esc_html($cf_distros_access_reference).'</CallerReference><Comment>'.esc_html(sprintf(_x('Created by s2Member, for S3 Bucket: %s.', 's2member-admin', 's2member'), $s3c['bucket'])).'</Comment></CloudFrontOriginAccessIdentityConfig>';
1307 $cf_args = array('method' => 'POST', 'redirection' => 5, 'body' => $cf_distros_access_xml, 'headers' => array('Host' => $cf_domain, 'Content-Type' => 'application/xml', 'Date' => $cf_date, 'Authorization' => 'AWS '.$cfc['access_key'].':'.$cf_signature));
1308
1309 if(($cf_response = c_ws_plugin__s2member_utils_urls::remote('https://'.$cf_domain.$cf_location, FALSE, array_merge($cf_args, array('timeout' => 20)), 'array')) && ($cf_response['code'] === 200 || $cf_response['code'] === 201))
1310 {
1311 if(preg_match('/\<CloudFrontOriginAccessIdentity.*?\>(.+?)\<\/CloudFrontOriginAccessIdentity\>/is', $cf_response['body'], $cf_distros_access_tag) && preg_match('/\<Id\>(.+?)\<\/Id\>/is', $cf_distros_access_tag[1], $cf_distros_access_id_tag) && preg_match('/\<S3CanonicalUserId\>(.+?)\<\/S3CanonicalUserId\>/is', $cf_distros_access_tag[1], $cf_distros_s3_access_id_tag))
1312 return array('success' => TRUE, 'code' => NULL, 'message' => NULL, 'distros_access_id' => trim($cf_distros_access_id_tag[1]), 'distros_s3_access_id' => trim($cf_distros_s3_access_id_tag[1]));
1313
1314 else // Else, we use a default error code and message.
1315 return array('success' => FALSE, 'code' => -98, 'message' => _x('Unable to create/read Amazon CloudFront Origin Access Identity. Unexpected response.', 's2member-admin', 's2member'));
1316 }
1317 else if(isset($cf_response['code'], $cf_response['message']))
1318 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon CloudFront API call. Feel free to exclude `%s` if you like. */
1319 return array('success' => FALSE, 'code' => $cf_response['code'], 'message' => sprintf(_x('Unable to create Amazon CloudFront Origin Access Identity. %s', 's2member-admin', 's2member'), $cf_response['message']));
1320
1321 else // Else, we use a default error code and message.
1322 return array('success' => FALSE, 'code' => -99, 'message' => _x('Unable to create Amazon CloudFront Origin Access Identity. Connection failed.', 's2member-admin', 's2member'));
1323 }
1324
1325 /**
1326 * Acquires an Amazon S3/CloudFront Distro.
1327 *
1328 * @package s2Member\Files
1329 * @since 110926
1330 *
1331 * @param string $distro_id Required. A Distro ID.
1332 * @param string $distro_type Required: `downloads|streaming`.
1333 *
1334 * @return array Array containing a true `success` and `etag`, `xml`, `deployed` elements on success, else a failure array.
1335 * Failure array will contain a failure `code`, and a failure `message`.
1336 */
1337 public static function amazon_cf_get_distro($distro_id = '', $distro_type = '')
1338 {
1339 if($distro_id && is_string($distro_id) && $distro_type && is_string($distro_type) && in_array($distro_type, array('downloads', 'streaming')))
1340 {
1341 foreach($GLOBALS['WS_PLUGIN__']['s2member']['o'] as $option => $option_value)
1342 if(preg_match('/^amazon_cf_files_/', $option) && ($option = preg_replace('/^amazon_cf_files_/', '', $option)))
1343 $cfc[$option] = $option_value;
1344
1345 $s3c['bucket'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_bucket'];
1346 $cfc['access_key'] = $s3c['access_key'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_access_key'];
1347 $cfc['secret_key'] = $s3c['secret_key'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_secret_key'];
1348
1349 $cf_domain = 'cloudfront.amazonaws.com';
1350 $cf_date = gmdate('D, d M Y H:i:s').' GMT';
1351 $cf_signature = base64_encode(c_ws_plugin__s2member_files_in::amazon_cf_sign($cf_date));
1352 $cf_location = ($distro_type === 'streaming') ? '/2010-11-01/streaming-distribution/'.$distro_id : '/2010-11-01/distribution/'.$distro_id;
1353 $cf_args = array('method' => 'GET', 'redirection' => 5, 'headers' => array('Host' => $cf_domain, 'Date' => $cf_date, 'Authorization' => 'AWS '.$cfc['access_key'].':'.$cf_signature));
1354
1355 if(($cf_response = c_ws_plugin__s2member_utils_urls::remote('https://'.$cf_domain.$cf_location, FALSE, array_merge($cf_args, array('timeout' => 20)), 'array')) && (($cf_response['code'] === 404 && $cf_response['message']) || ($cf_response['code'] === 200 && !empty($cf_response['headers']['etag']) && !empty($cf_response['body']))))
1356 {
1357 if($cf_response['code'] === 200 && !empty($cf_response['headers']['etag']) && !empty($cf_response['body']))
1358 return array('success' => TRUE, 'code' => NULL, 'message' => NULL, 'etag' => trim($cf_response['headers']['etag']), 'xml' => trim($cf_response['body']), 'deployed' => ((stripos($cf_response['body'], '<Status>Deployed</Status>') !== FALSE) ? TRUE : FALSE));
1359
1360 else /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon CloudFront API call. Feel free to exclude `%s` if you like. */
1361 return array('success' => FALSE, 'code' => $cf_response['code'], 'message' => sprintf(_x('Existing Amazon CloudFront Distro NOT found. %s', 's2member-admin', 's2member'), $cf_response['message']));
1362 }
1363 else if(isset($cf_response['code'], $cf_response['message']))
1364 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon CloudFront API call. Feel free to exclude `%s` if you like. */
1365 return array('success' => FALSE, 'code' => $cf_response['code'], 'message' => sprintf(_x('Unable to acquire existing Amazon CloudFront Distro. %s', 's2member-admin', 's2member'), $cf_response['message']));
1366
1367 else // Else, we use a default error code and message.
1368 return array('success' => FALSE, 'code' => -98, 'message' => _x('Unable to acquire existing Amazon CloudFront Distro. Connection failed.', 's2member-admin', 's2member'));
1369 }
1370 else // Else, we use a default error code and message.
1371 return array('success' => FALSE, 'code' => -99, 'message' => _x('Unable to acquire existing Amazon CloudFront Distro. Invalid Distro ID and/or Distro type.', 's2member-admin', 's2member'));
1372 }
1373
1374 /**
1375 * Disables an Amazon S3/CloudFront Distro.
1376 *
1377 * @package s2Member\Files
1378 * @since 110926
1379 *
1380 * @param string $distro_id Required. A Distro ID.
1381 * @param string $distro_id_etag Required. A Distro ETag header.
1382 * @param string $distro_id_xml Required. A Distro's XML configuration.
1383 *
1384 * @return array Array containing a true `success` and `etag`, `xml`, `deployed` elements on success, else a failure array.
1385 * Failure array will contain a failure `code`, and a failure `message`.
1386 */
1387 public static function amazon_cf_disable_distro($distro_id = '', $distro_id_etag = '', $distro_id_xml = '')
1388 {
1389 if($distro_id && is_string($distro_id) && $distro_id_etag && is_string($distro_id_etag) && $distro_id_xml && is_string($distro_id_xml) && ($distro_id_type = (stripos($distro_id_xml, '<StreamingDistribution') !== FALSE) ? 'streaming' : ((stripos($distro_id_xml, '<Distribution') !== FALSE) ? 'downloads' : FALSE)) && preg_match('/\<CallerReference\>(.+?)\<\/CallerReference\>/is', $distro_id_xml, $distro_id_reference_tag) && ($distro_id_reference = $distro_id_reference_tag[1]))
1390 {
1391 if(stripos($distro_id_xml, '<Enabled>false</Enabled>') === FALSE) // Only if it has NOT already been disabled. We do NOT need to do it again.
1392 {
1393 if(stripos($distro_id_xml, '<Status>Deployed</Status>') !== FALSE) // Check distro status before we even begin processing.
1394 {
1395 foreach($GLOBALS['WS_PLUGIN__']['s2member']['o'] as $option => $option_value)
1396 if(preg_match('/^amazon_cf_files_/', $option) && ($option = preg_replace('/^amazon_cf_files_/', '', $option)))
1397 $cfc[$option] = $option_value;
1398
1399 $s3c['bucket'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_bucket'];
1400 $cfc['access_key'] = $s3c['access_key'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_access_key'];
1401 $cfc['secret_key'] = $s3c['secret_key'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_secret_key'];
1402
1403 $cf_domain = 'cloudfront.amazonaws.com';
1404 $cf_date = gmdate('D, d M Y H:i:s').' GMT';
1405 $cf_signature = base64_encode(c_ws_plugin__s2member_files_in::amazon_cf_sign($cf_date));
1406 $cf_location = ($distro_id_type === 'streaming') ? '/2010-11-01/streaming-distribution/'.$distro_id.'/config' : '/2010-11-01/distribution/'.$distro_id.'/config';
1407 $cf_distro_xml = ($distro_id_type === 'streaming') ? '<?xml version="1.0" encoding="UTF-8"?><StreamingDistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2010-11-01/"><S3Origin><DNSName>'.esc_html($s3c['bucket']).'.s3.amazonaws.com</DNSName></S3Origin><CallerReference>'.esc_html($distro_id_reference).'</CallerReference><Enabled>false</Enabled><TrustedSigners><Self/></TrustedSigners></StreamingDistributionConfig>' : '<?xml version="1.0" encoding="UTF-8"?><DistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2010-11-01/"><S3Origin><DNSName>'.esc_html($s3c['bucket']).'.s3.amazonaws.com</DNSName></S3Origin><CallerReference>'.esc_html($distro_id_reference).'</CallerReference><Enabled>false</Enabled><TrustedSigners><Self/></TrustedSigners></DistributionConfig>';
1408 $cf_args = array('method' => 'PUT', 'redirection' => 5, 'body' => $cf_distro_xml, 'headers' => array('Host' => $cf_domain, 'Content-Type' => 'application/xml', 'Date' => $cf_date, 'If-Match' => $distro_id_etag, 'Authorization' => 'AWS '.$cfc['access_key'].':'.$cf_signature));
1409
1410 if(($cf_response = c_ws_plugin__s2member_utils_urls::remote('https://'.$cf_domain.$cf_location, FALSE, array_merge($cf_args, array('timeout' => 20)), 'array')) && $cf_response['code'] === 200 && !empty($cf_response['headers']['etag']) && !empty($cf_response['body']))
1411 return array('success' => TRUE, 'code' => NULL, 'message' => NULL, 'etag' => trim($cf_response['headers']['etag']), 'xml' => trim($cf_response['body']), 'deployed' => ((stripos($cf_response['body'], '<Status>Deployed</Status>') !== FALSE) ? TRUE : FALSE));
1412
1413 else if(isset($cf_response['code'], $cf_response['message']))
1414 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon CloudFront API call. Feel free to exclude `%s` if you like. */
1415 return array('success' => FALSE, 'code' => $cf_response['code'], 'message' => sprintf(_x('Unable to disable existing Amazon CloudFront Distro. %s', 's2member-admin', 's2member'), $cf_response['message']));
1416
1417 else // Else, we use a default error code and message.
1418 return array('success' => FALSE, 'code' => -97, 'message' => _x('Unable to disable existing Amazon CloudFront Distro. Connection failed.', 's2member-admin', 's2member'));
1419 }
1420 else // Else, we use a default error code and message.
1421 return array('success' => FALSE, 'code' => -98, 'message' => _x('Existing Amazon CloudFront Distro cannot be disabled at this time. Still in a `pending` state. Please wait 15 minutes, then try again. There is a certain process that s2Member must strictly adhere to when re-configuring your Amazon CloudFront Distros. You may have to tick the auto-configure checkbox again, and re-run s2Member\'s auto-configuration routine many times, because s2Member will likely run into several `pending` challenges, as it works to completely re-configure your Amazon CloudFront Distros for you. Thanks for your patience. Please wait 15 minutes, then try again.', 's2member-admin', 's2member'));
1422 }
1423 else // Else, we use a default error code and message.
1424 return array('success' => TRUE, 'code' => NULL, 'message' => NULL, 'etag' => $distro_id_etag, 'xml' => $distro_id_xml, 'deployed' => ((stripos($distro_id_xml, '<Status>Deployed</Status>') !== FALSE) ? TRUE : FALSE));
1425 }
1426 else // Else, we use a default error code and message.
1427 return array('success' => FALSE, 'code' => -99, 'message' => _x('Unable to disable existing Amazon CloudFront Distro. Invalid Distro ID, ETag, or XML config.', 's2member-admin', 's2member'));
1428 }
1429
1430 /**
1431 * Deletes an Amazon S3/CloudFront Distro.
1432 *
1433 * @package s2Member\Files
1434 * @since 110926
1435 *
1436 * @param string $distro_id Required. A Distro ID.
1437 * @param string $distro_id_etag Required. A Distro ETag header.
1438 * @param string $distro_id_xml Required. A Distro's XML configuration.
1439 *
1440 * @return array Array containing a true `success` element on success, else a failure array.
1441 * Failure array will contain a failure `code`, and a failure `message`.
1442 */
1443 public static function amazon_cf_del_distro($distro_id = '', $distro_id_etag = '', $distro_id_xml = '')
1444 {
1445 if($distro_id && is_string($distro_id) && $distro_id_etag && is_string($distro_id_etag) && $distro_id_xml && is_string($distro_id_xml) && ($distro_id_type = (stripos($distro_id_xml, '<StreamingDistribution') !== FALSE) ? 'streaming' : ((stripos($distro_id_xml, '<Distribution') !== FALSE) ? 'downloads' : FALSE)) && preg_match('/\<CallerReference\>(.+?)\<\/CallerReference\>/is', $distro_id_xml, $distro_id_reference_tag) && ($distro_id_reference = $distro_id_reference_tag[1]))
1446 {
1447 if(stripos($distro_id_xml, '<Status>Deployed</Status>') !== FALSE) // Check distro status before we even begin processing this deletion.
1448 {
1449 if(($cf_response = c_ws_plugin__s2member_files_in::amazon_cf_disable_distro($distro_id, $distro_id_etag, $distro_id_xml)) && $cf_response['success'])
1450 {
1451 if(($cf_response = c_ws_plugin__s2member_files_in::amazon_cf_get_distro($distro_id, $distro_id_type)) && $cf_response['success'] && $cf_response['deployed'])
1452 {
1453 foreach($GLOBALS['WS_PLUGIN__']['s2member']['o'] as $option => $option_value)
1454 if(preg_match('/^amazon_cf_files_/', $option) && ($option = preg_replace('/^amazon_cf_files_/', '', $option)))
1455 $cfc[$option] = $option_value;
1456
1457 $s3c['bucket'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_bucket'];
1458 $cfc['access_key'] = $s3c['access_key'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_access_key'];
1459 $cfc['secret_key'] = $s3c['secret_key'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_secret_key'];
1460
1461 $cf_domain = 'cloudfront.amazonaws.com';
1462 $cf_date = gmdate('D, d M Y H:i:s').' GMT';
1463 $cf_signature = base64_encode(c_ws_plugin__s2member_files_in::amazon_cf_sign($cf_date));
1464 $cf_location = ($distro_id_type === 'streaming') ? '/2010-11-01/streaming-distribution/'.$distro_id : '/2010-11-01/distribution/'.$distro_id;
1465 $cf_args = array('method' => 'DELETE', 'redirection' => 5, 'headers' => array('Host' => $cf_domain, 'Date' => $cf_date, 'If-Match' => $cf_response['etag'], 'Authorization' => 'AWS '.$cfc['access_key'].':'.$cf_signature));
1466
1467 if(($cf_response = c_ws_plugin__s2member_utils_urls::remote('https://'.$cf_domain.$cf_location, FALSE, array_merge($cf_args, array('timeout' => 20)), 'array')) && ($cf_response['code'] === 200 || $cf_response['code'] === 204))
1468 return array('success' => TRUE, 'code' => NULL, 'message' => NULL); // Deleted successfully.
1469
1470 else if(isset($cf_response['code'], $cf_response['message']))
1471 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon CloudFront API call. Feel free to exclude `%s` if you like. */
1472 return array('success' => FALSE, 'code' => $cf_response['code'], 'message' => sprintf(_x('Unable to delete existing Amazon CloudFront Distro. %s', 's2member-admin', 's2member'), $cf_response['message']));
1473
1474 else // Else, we use a default error code and message.
1475 return array('success' => FALSE, 'code' => -94, 'message' => _x('Unable to delete existing Amazon CloudFront Distro. Connection failed.', 's2member-admin', 's2member'));
1476 }
1477 else if(isset($cf_response['success'], $cf_response['deployed']) && $cf_response['success'] && !$cf_response['deployed'])
1478 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon CloudFront API call. Feel free to exclude `%s` if you like. */
1479 return array('success' => FALSE, 'code' => -95, 'message' => _x('Existing Amazon CloudFront Distro cannot be deleted at this time. Still in a `pending` state after having been disabled by s2Member. Please wait 15 minutes, then try again. There is a certain process that s2Member must strictly adhere to when re-configuring your Amazon CloudFront Distros. You may have to tick the auto-configure checkbox again, and re-run s2Member\'s auto-configuration routine many times, because s2Member will likely run into several `pending` challenges, as it works to completely re-configure your Amazon CloudFront Distros for you. Thanks for your patience. Please wait 15 minutes, then try again.', 's2member-admin', 's2member'));
1480
1481 else if(isset($cf_response['code'], $cf_response['message']))
1482 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon CloudFront API call. Feel free to exclude `%s` if you like. */
1483 return array('success' => FALSE, 'code' => $cf_response['code'], 'message' => sprintf(_x('Unable to check status of existing Amazon CloudFront Distro. %s', 's2member-admin', 's2member'), $cf_response['message']));
1484
1485 else // Else, we use a default error code and message.
1486 return array('success' => FALSE, 'code' => -96, 'message' => _x('Unable to check status of existing Amazon CloudFront Distro. Connection failed.', 's2member-admin', 's2member'));
1487 }
1488 else if(isset($cf_response['code'], $cf_response['message']))
1489 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon CloudFront API call. Feel free to exclude `%s` if you like. */
1490 return array('success' => FALSE, 'code' => $cf_response['code'], 'message' => sprintf(_x('Unable to disable existing Amazon CloudFront Distro. %s', 's2member-admin', 's2member'), $cf_response['message']));
1491
1492 else // Else, we use a default error code and message.
1493 return array('success' => FALSE, 'code' => -97, 'message' => _x('Unable to disable existing Amazon CloudFront Distro. Connection failed.', 's2member-admin', 's2member'));
1494 }
1495 else // Else, we use a default error code and message.
1496 return array('success' => FALSE, 'code' => -98, 'message' => _x('Existing Amazon CloudFront Distro cannot be deleted at this time. Still in a `pending` state. Please wait 15 minutes, then try again. There is a certain process that s2Member must strictly adhere to when re-configuring your Amazon CloudFront Distros. You may have to tick the auto-configure checkbox again, and re-run s2Member\'s auto-configuration routine many times, because s2Member will likely run into several `pending` challenges, as it works to completely re-configure your Amazon CloudFront Distros for you. Thanks for your patience. Please wait 15 minutes, then try again.', 's2member-admin', 's2member'));
1497 }
1498 else // Else, we use a default error code and message.
1499 return array('success' => FALSE, 'code' => -99, 'message' => _x('Unable to delete existing Amazon CloudFront Distro. Invalid Distro ID or ETag.', 's2member-admin', 's2member'));
1500 }
1501
1502 /**
1503 * Creates an Amazon S3/CloudFront Distro.
1504 *
1505 * @package s2Member\Files
1506 * @since 110926
1507 *
1508 * @param string $distro_type Required: `downloads|streaming`.
1509 *
1510 * @return array Array containing a true `success` and `distro_[distro_type]_id`, `distro_[distro_type]_dname` elements on success, else a failure array.
1511 * Failure array will contain a failure `code`, and a failure `message`.
1512 */
1513 public static function amazon_cf_create_distro($distro_type = '')
1514 {
1515 if($distro_type && is_string($distro_type) && in_array($distro_type, array('downloads', 'streaming')))
1516 {
1517 foreach($GLOBALS['WS_PLUGIN__']['s2member']['o'] as $option => $option_value)
1518 if(preg_match('/^amazon_cf_files_/', $option) && ($option = preg_replace('/^amazon_cf_files_/', '', $option)))
1519 $cfc[$option] = $option_value;
1520
1521 $s3c['bucket'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_bucket'];
1522 $cfc['access_key'] = $s3c['access_key'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_access_key'];
1523 $cfc['secret_key'] = $s3c['secret_key'] = $GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_s3_files_secret_key'];
1524
1525 $cf_domain = 'cloudfront.amazonaws.com';
1526 $cf_date = gmdate('D, d M Y H:i:s').' GMT';
1527 $cf_signature = base64_encode(c_ws_plugin__s2member_files_in::amazon_cf_sign($cf_date));
1528
1529 if($distro_type === 'downloads') // Create a `downloads` Distro? This uses a different XML schema.
1530 {
1531 $cf_location = '/2010-11-01/distribution'; // Create distro.
1532 $cf_distro_downloads_reference = time().'.'.md5('downloads'.$s3c['bucket'].$s3c['access_key'].$s3c['secret_key'].$cfc['private_key'].$cfc['private_key_id'].$cfc['distro_downloads_cname']);
1533 $cf_distro_downloads_xml = '<?xml version="1.0" encoding="UTF-8"?><DistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2010-11-01/"><S3Origin><DNSName>'.esc_html($s3c['bucket']).'.s3.amazonaws.com</DNSName><OriginAccessIdentity>origin-access-identity/cloudfront/'.esc_html($cfc['distros_access_id']).'</OriginAccessIdentity></S3Origin><CallerReference>'.esc_html($cf_distro_downloads_reference).'</CallerReference>'.(($cfc['distro_downloads_cname']) ? '<CNAME>'.esc_html($cfc['distro_downloads_cname']).'</CNAME>' : '').'<Comment>'.esc_html(sprintf(_x('Created by s2Member, for S3 Bucket: %s.', 's2member-admin', 's2member'), $s3c['bucket'])).'</Comment><Enabled>true</Enabled><DefaultRootObject>index.html</DefaultRootObject><TrustedSigners><Self/></TrustedSigners></DistributionConfig>';
1534 $cf_args = array('method' => 'POST', 'redirection' => 5, 'body' => $cf_distro_downloads_xml, 'headers' => array('Host' => $cf_domain, 'Content-Type' => 'application/xml', 'Date' => $cf_date, 'Authorization' => 'AWS '.$cfc['access_key'].':'.$cf_signature));
1535
1536 if(($cf_response = c_ws_plugin__s2member_utils_urls::remote('https://'.$cf_domain.$cf_location, FALSE, array_merge($cf_args, array('timeout' => 20)), 'array')) && ($cf_response['code'] === 200 || $cf_response['code'] === 201))
1537 {
1538 if(preg_match('/\<Distribution.*?\>(.+?)\<\/Distribution\>/is', $cf_response['body'], $cf_distro_downloads_tag) && preg_match('/\<Id\>(.+?)\<\/Id\>/is', $cf_distro_downloads_tag[1], $cf_distro_downloads_id_tag) && preg_match('/\<DomainName\>(.+?)\<\/DomainName\>/is', $cf_distro_downloads_tag[1], $cf_distro_downloads_dname_tag))
1539 return array('success' => TRUE, 'code' => NULL, 'message' => NULL, 'distro_downloads_id' => trim($cf_distro_downloads_id_tag[1]), 'distro_downloads_dname' => trim($cf_distro_downloads_dname_tag[1]));
1540
1541 else // Else, we use a default error code and message.
1542 return array('success' => FALSE, 'code' => -97, 'message' => _x('Unable to create/read Amazon CloudFront Downloads Distro. Unexpected response.', 's2member-admin', 's2member'));
1543 }
1544 else if(isset($cf_response['code'], $cf_response['message']))
1545 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon CloudFront API call. Feel free to exclude `%s` if you like. */
1546 return array('success' => FALSE, 'code' => $cf_response['code'], 'message' => sprintf(_x('Unable to create Amazon CloudFront Downloads Distro. %s', 's2member-admin', 's2member'), $cf_response['message']));
1547
1548 else // Else, we use a default error code and message.
1549 return array('success' => FALSE, 'code' => -98, 'message' => _x('Unable to create Amazon CloudFront Downloads Distro. Connection failed.', 's2member-admin', 's2member'));
1550 }
1551 else if($distro_type === 'streaming') // Create a `streaming` Distro? A different XML schema.
1552 {
1553 $cf_location = '/2010-11-01/streaming-distribution'; // Create streaming distro.
1554 $cf_distro_streaming_reference = time().'.'.md5('streaming'.$s3c['bucket'].$s3c['access_key'].$s3c['secret_key'].$cfc['private_key'].$cfc['private_key_id'].$cfc['distro_streaming_cname']);
1555 $cf_distro_streaming_xml = '<?xml version="1.0" encoding="UTF-8"?><StreamingDistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2010-11-01/"><S3Origin><DNSName>'.esc_html($s3c['bucket']).'.s3.amazonaws.com</DNSName><OriginAccessIdentity>origin-access-identity/cloudfront/'.esc_html($cfc['distros_access_id']).'</OriginAccessIdentity></S3Origin><CallerReference>'.esc_html($cf_distro_streaming_reference).'</CallerReference>'.(($cfc['distro_streaming_cname']) ? '<CNAME>'.esc_html($cfc['distro_streaming_cname']).'</CNAME>' : '').'<Comment>'.esc_html(sprintf(_x('Created by s2Member, for S3 Bucket: %s.', 's2member-admin', 's2member'), $s3c['bucket'])).'</Comment><Enabled>true</Enabled><DefaultRootObject>index.html</DefaultRootObject><TrustedSigners><Self/></TrustedSigners></StreamingDistributionConfig>';
1556 $cf_args = array('method' => 'POST', 'redirection' => 5, 'body' => $cf_distro_streaming_xml, 'headers' => array('Host' => $cf_domain, 'Content-Type' => 'application/xml', 'Date' => $cf_date, 'Authorization' => 'AWS '.$cfc['access_key'].':'.$cf_signature));
1557
1558 if(($cf_response = c_ws_plugin__s2member_utils_urls::remote('https://'.$cf_domain.$cf_location, FALSE, array_merge($cf_args, array('timeout' => 20)), 'array')) && ($cf_response['code'] === 200 || $cf_response['code'] === 201))
1559 {
1560 if(preg_match('/\<StreamingDistribution.*?\>(.+?)\<\/StreamingDistribution\>/is', $cf_response['body'], $cf_distro_streaming_tag) && preg_match('/\<Id\>(.+?)\<\/Id\>/is', $cf_distro_streaming_tag[1], $cf_distro_streaming_id_tag) && preg_match('/\<DomainName\>(.+?)\<\/DomainName\>/is', $cf_distro_streaming_tag[1], $cf_distro_streaming_dname_tag))
1561 return array('success' => TRUE, 'code' => NULL, 'message' => NULL, 'distro_streaming_id' => trim($cf_distro_streaming_id_tag[1]), 'distro_streaming_dname' => trim($cf_distro_streaming_dname_tag[1]));
1562
1563 else // Else, we use a default error code and message.
1564 return array('success' => FALSE, 'code' => -97, 'message' => _x('Unable to create/read Amazon CloudFront Streaming Distro. Unexpected response.', 's2member-admin', 's2member'));
1565 }
1566 else if(isset($cf_response['code'], $cf_response['message']))
1567 /* translators: In this translation, `%s` may be filled with an English message, which comes from the Amazon CloudFront API call. Feel free to exclude `%s` if you like. */
1568 return array('success' => FALSE, 'code' => $cf_response['code'], 'message' => sprintf(_x('Unable to create Amazon CloudFront Streaming Distro. %s', 's2member-admin', 's2member'), $cf_response['message']));
1569
1570 else // Else, we use a default error code and message.
1571 return array('success' => FALSE, 'code' => -98, 'message' => _x('Unable to create Amazon CloudFront Streaming Distro. Connection failed.', 's2member-admin', 's2member'));
1572 }
1573 }
1574 // Else, we use a default error code and message (default behavior).
1575 return array('success' => FALSE, 'code' => -99, 'message' => _x('Unable to create Amazon CloudFront Distro. Invalid Distro type.', 's2member-admin', 's2member'));
1576 }
1577
1578 /**
1579 * Resets Amazon S3/CloudFront configuration.
1580 *
1581 * @package s2Member\Files
1582 * @since 140812
1583 */
1584 public static function reset_aws_cf_config_values()
1585 {
1586 c_ws_plugin__s2member_menu_pages::update_all_options(
1587 array(
1588 'ws_plugin__s2member_amazon_cf_files_private_key' => '',
1589 'ws_plugin__s2member_amazon_cf_files_private_key_id' => '',
1590 'ws_plugin__s2member_amazon_cf_files_distros_access_id' => '',
1591 'ws_plugin__s2member_amazon_cf_files_distros_s3_access_id' => '',
1592 'ws_plugin__s2member_amazon_cf_files_distro_downloads_id' => '',
1593 'ws_plugin__s2member_amazon_cf_files_distro_downloads_cname' => '',
1594 'ws_plugin__s2member_amazon_cf_files_distro_downloads_dname' => '',
1595 'ws_plugin__s2member_amazon_cf_files_distro_streaming_id' => '',
1596 'ws_plugin__s2member_amazon_cf_files_distro_streaming_cname' => '',
1597 'ws_plugin__s2member_amazon_cf_files_distro_streaming_dname' => '',
1598 'ws_plugin__s2member_amazon_cf_files_distros_auto_config_status' => ''
1599 ), TRUE, FALSE, FALSE, FALSE, FALSE
1600 );
1601 }
1602 }
1603 }
1604