PluginProbe
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions / 260917
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions v260917
260917 260913 260909 260829 260814 260805 110710 110731 110812 110815 110912 110913 110915 110926 110927 111002 111003 111011 111017 111029 111105 111206 111216 111220 120213 All 189 releases
s2member / src / includes / classes / menu-pages.inc.php

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

1,100 lines 64.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // @codingStandardsIgnoreFile
3 /**
4 * Administrative menu pages.
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\Menu_Pages
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_menu_pages'))
22 {
23 /**
24 * Administrative menu pages.
25 *
26 * @package s2Member\Menu_Pages
27 * @since 3.5
28 */
29 class c_ws_plugin__s2member_menu_pages
30 {
31 /**
32 * Pre-display errors.
33 *
34 * @package s2Member\Menu_Pages
35 * @since 111209
36 *
37 * @var array
38 */
39 public static $pre_display_errors = array();
40
41 /**
42 * Saves all options from any menu page.
43 *
44 * Can also be self-verified; and configured extensively with function parameters.
45 *
46 * @package s2Member\Menu_Pages
47 * @since 3.5
48 *
49 * @param null|array $new_options Optional. Force feed an array of new options. Defaults to ``$_POST`` vars.
50 * If ``$new_options`` are passed in, be SURE that you've already applied ``stripslashes_deep()``.
51 * @param bool $verified Optional. Defaults to false. If true, ``wp_verify_nonce()`` is skipped in this routine.
52 * @param bool $update_other Optional. Defaults to true. If false, other option-dependent routines will not be processed.
53 * @param bool|array $display_notices Optional. Defaults to true. Can be false, or an array of certain notices that can be displayed.
54 * @param bool|array $enqueue_notices Optional. Defaults to false. Can be true, or an array of certain notices that should be enqueued.
55 * @param bool $request_refresh Optional. Defaults to false. If true, resulting `success` notice will include a link to refresh the menu page.
56 *
57 * @return bool True if all s2Member options were updated successfully, else false.
58 */
59 public static function update_all_options($new_options = NULL, $verified = FALSE, $update_other = TRUE, $display_notices = TRUE, $enqueue_notices = FALSE, $request_refresh = FALSE)
60 {
61 $updated_all_options = FALSE; // Initializing this variable here makes it an available reference-variable to Hooks/Filters.
62
63 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
64 do_action('ws_plugin__s2member_before_update_all_options', get_defined_vars()); // If you use this Hook, be sure to use ``wp_verify_nonce()``.
65 unset($__refs, $__v); // Housekeeping.
66
67 if($verified || (!empty($_POST['ws_plugin__s2member_options_save']) && ($nonce = $_POST['ws_plugin__s2member_options_save']) && wp_verify_nonce($nonce, 'ws-plugin--s2member-options-save')))
68 {
69 $options = $GLOBALS['WS_PLUGIN__']['s2member']['o']; // Acquire the full existing configuration options array here.
70
71 $new_options = (is_array($new_options)) ? $new_options : ((!empty($_POST) && is_array($_POST)) ? stripslashes_deep($_POST) : array());
72 $new_options = c_ws_plugin__s2member_utils_strings::trim_deep($new_options);
73
74 foreach($new_options as $key => $value) // Find all keys contained within ``$new_options`` matching `^ws_plugin__s2member_`.
75 if(strpos($key, 'ws_plugin__s2member_') === 0) // A relevant ``$new_options`` key matching `^ws_plugin__s2member_`?
76
77 if($key === 'ws_plugin__s2member_configured') // s2Member is now configured (according to these options)?
78 ($GLOBALS['WS_PLUGIN__']['s2member']['c']['configured'] = $value).update_option('ws_plugin__s2member_configured', $value);
79
80 else if(!is_array($value) || (is_array($value) && array_shift($value) === 'update-signal')) // Updating an array?
81 $options[preg_replace('/^'.preg_quote('ws_plugin__s2member_', '/').'/', '', $key)] = $value;
82
83 unset($key, $value); // Unset these utility variables now. This prevents bleeding vars into Hooks/Filters that are of no use.
84
85 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
86 do_action('ws_plugin__s2member_during_update_all_options', get_defined_vars());
87 unset($__refs, $__v); // Housekeeping.
88
89 //260812 Keep the old s2Get option synchronized for rollback compatibility, while accepting legacy updates during the transition.
90 if(isset($new_options['ws_plugin__s2member_sc_s2get_userid_whitelist']) && !isset($new_options['ws_plugin__s2member_sc_user_fields_whitelist']))
91 $options['sc_user_fields_whitelist'] = $options['sc_s2get_userid_whitelist'];
92 if(isset($options['sc_user_fields_whitelist']))
93 $options['sc_s2get_userid_whitelist'] = $options['sc_user_fields_whitelist'];
94
95 $options = ws_plugin__s2member_configure_options_and_their_defaults(($options = array_merge($options, array('options_version' => (string)($options['options_version'] + 0.001)))));
96 update_option('ws_plugin__s2member_options', $options).((is_multisite() && is_main_site()) ? update_site_option('ws_plugin__s2member_options', $options) : NULL).update_option('ws_plugin__s2member_cache', array());
97
98 if($update_other === TRUE || in_array('auto_eot_system', (array)$update_other)) // Handle the Auto-EOT System now (enable/disable).
99 ($options['auto_eot_system_enabled'] == 1) ? c_ws_plugin__s2member_auto_eots::add_auto_eot_system() : c_ws_plugin__s2member_auto_eots::delete_auto_eot_system();
100
101 if(($display_notices === TRUE || in_array('success', (array)$display_notices)) && ($notice = '<strong>Options saved.'.(($request_refresh) ? ' Please <a href="'.esc_attr($_SERVER['REQUEST_URI']).'">refresh</a>.' : '').'</strong>'))
102 ($enqueue_notices === TRUE || in_array('success', (array)$enqueue_notices)) ? c_ws_plugin__s2member_admin_notices::enqueue_admin_notice($notice, '*:*') : c_ws_plugin__s2member_admin_notices::display_admin_notice($notice);
103
104 if(empty($_GET['page']) || $_GET['page'] !== 'ws-plugin--s2member-mms-ops') // Do NOT display page-conflict-warnings on the Main Multisite Configuration panel.
105 {
106 if(!$options['membership_options_page'] && ($display_notices === TRUE || in_array('page-conflict-warnings', (array)$display_notices)) && ($notice = '<strong>NOTE:</strong> s2Member security restrictions will NOT be enforced until you\'ve configured a Membership Options Page. See: <strong>s2Member → General Options → Membership Options Page</strong>.'))
107 ($enqueue_notices === TRUE || in_array('page-conflict-warnings', (array)$enqueue_notices)) ? c_ws_plugin__s2member_admin_notices::enqueue_admin_notice($notice, '*:*', TRUE) : c_ws_plugin__s2member_admin_notices::display_admin_notice($notice, TRUE);
108
109 if($options['login_welcome_page'] && $options['login_welcome_page'] === $options['membership_options_page'] && ($display_notices === TRUE || in_array('page-conflict-warnings', (array)$display_notices)) && ($notice = '<strong>s2Member:</strong> Your Login Welcome Page is the same as your Membership Options Page. Please correct this. See: <strong>s2Member → General Options → Login Welcome Page</strong>.'))
110 ($enqueue_notices === TRUE || in_array('page-conflict-warnings', (array)$enqueue_notices)) ? c_ws_plugin__s2member_admin_notices::enqueue_admin_notice($notice, '*:*', TRUE) : c_ws_plugin__s2member_admin_notices::display_admin_notice($notice, TRUE);
111
112 if($options['membership_options_page'] && (string)get_option('page_on_front') === $options['membership_options_page'] && ($display_notices === TRUE || in_array('page-conflict-warnings', (array)$display_notices)) && ($notice = '<strong>s2Member:</strong> Your Membership Options Page is currently configured as your Home Page (i.e., static page) for WordPress. This causes internal conflicts with s2Member. Your Membership Options Page MUST stand alone. Please correct this. See: <strong>WordPress → Reading Options</strong>. Or change: <strong>s2Member → General Options → Membership Options Page</strong>.'))
113 ($enqueue_notices === TRUE || in_array('page-conflict-warnings', (array)$enqueue_notices)) ? c_ws_plugin__s2member_admin_notices::enqueue_admin_notice($notice, '*:*', TRUE) : c_ws_plugin__s2member_admin_notices::display_admin_notice($notice, TRUE);
114
115 if($options['login_welcome_page'] && (string)get_option('page_on_front') === $options['login_welcome_page'] && ($display_notices === TRUE || in_array('page-conflict-warnings', (array)$display_notices)) && ($notice = '<strong>s2Member:</strong> Your Login Welcome Page is currently configured as your Home Page (i.e., static page) for WordPress. This causes internal conflicts with s2Member. Your Login Welcome Page MUST stand alone. Please correct this. See: <strong>WordPress → Reading Options</strong>. Or change: <strong>s2Member → General Options → Login Welcome Page</strong>.'))
116 ($enqueue_notices === TRUE || in_array('page-conflict-warnings', (array)$enqueue_notices)) ? c_ws_plugin__s2member_admin_notices::enqueue_admin_notice($notice, '*:*', TRUE) : c_ws_plugin__s2member_admin_notices::display_admin_notice($notice, TRUE);
117
118 if($options['membership_options_page'] && (string)get_option('page_for_posts') === $options['membership_options_page'] && ($display_notices === TRUE || in_array('page-conflict-warnings', (array)$display_notices)) && ($notice = '<strong>s2Member:</strong> Your Membership Options Page is currently configured as your Posts Page (i.e., static page) for WordPress. This causes internal conflicts with s2Member. Your Membership Options Page MUST stand alone. Please correct this. See: <strong>WordPress → Reading Options</strong>. Or change: <strong>s2Member → General Options → Membership Options Page</strong>.'))
119 ($enqueue_notices === TRUE || in_array('page-conflict-warnings', (array)$enqueue_notices)) ? c_ws_plugin__s2member_admin_notices::enqueue_admin_notice($notice, '*:*', TRUE) : c_ws_plugin__s2member_admin_notices::display_admin_notice($notice, TRUE);
120
121 if($options['login_welcome_page'] && (string)get_option('page_for_posts') === $options['login_welcome_page'] && ($display_notices === TRUE || in_array('page-conflict-warnings', (array)$display_notices)) && ($notice = '<strong>s2Member:</strong> Your Login Welcome Page is currently configured as your Posts Page (i.e., static page) for WordPress. This causes internal conflicts with s2Member. Your Login Welcome Page MUST stand alone. Please correct this. See: <strong>WordPress → Reading Options</strong>. Or change: <strong>s2Member → General Options → Login Welcome Page</strong>.'))
122 ($enqueue_notices === TRUE || in_array('page-conflict-warnings', (array)$enqueue_notices)) ? c_ws_plugin__s2member_admin_notices::enqueue_admin_notice($notice, '*:*', TRUE) : c_ws_plugin__s2member_admin_notices::display_admin_notice($notice, TRUE);
123
124 if($options['file_download_limit_exceeded_page'] && $options['file_download_limit_exceeded_page'] === $options['membership_options_page'] && ($display_notices === TRUE || in_array('page-conflict-warnings', (array)$display_notices)) && ($notice = '<strong>s2Member:</strong> Your Download Limit Exceeded Page is the same as your Membership Options Page. Please correct this. See: <strong>s2Member → Download Options</strong>.'))
125 ($enqueue_notices === TRUE || in_array('page-conflict-warnings', (array)$enqueue_notices)) ? c_ws_plugin__s2member_admin_notices::enqueue_admin_notice($notice, '*:*', TRUE) : c_ws_plugin__s2member_admin_notices::display_admin_notice($notice, TRUE);
126 }
127 $updated_all_options = TRUE; // Flag indicating this routine was processed successfully; and that all s2Member options have been updated successfully.
128 }
129 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
130 do_action('ws_plugin__s2member_after_update_all_options', get_defined_vars());
131 unset($__refs, $__v); // Housekeeping.
132
133 return apply_filters('ws_plugin__s2member_update_all_options', (($updated_all_options) ? TRUE : FALSE), get_defined_vars());
134 }
135
136 /**
137 * Displays shared End-of-Term demotion-role settings on gateway option pages.
138 *
139 * @package s2Member\Menu_Pages
140 * @since 260916.2004
141 *
142 * @return null
143 */
144 public static function eot_demotion_options()
145 {
146 $demotion_to_role = (string)$GLOBALS['WS_PLUGIN__']['s2member']['o']['eot_demotion_to_role'];
147 $demotion_from = (string)$GLOBALS['WS_PLUGIN__']['s2member']['o']['eot_demotion_from'];
148 $demotion_disabled = ($GLOBALS['WS_PLUGIN__']['s2member']['o']['membership_eot_behavior'] === 'delete');
149
150 //260917.0003 Keep Demote From/To visible but disabled when EOT behavior is Delete; JavaScript mirrors this immediately when the behavior selector changes.
151 echo '<tr class="ws-plugin--s2member-eot-demotion-option-row"'.(($demotion_disabled) ? ' style="opacity:0.5;"' : '').'>'."\n";
152 echo '<th><label for="ws-plugin--s2member-eot-demotion-from">Demote From:</label></th>'."\n";
153 echo '</tr>'."\n";
154 echo '<tr class="ws-plugin--s2member-eot-demotion-option-row"'.(($demotion_disabled) ? ' style="opacity:0.5;"' : '').'>'."\n";
155 echo '<td>'."\n";
156 echo '<select name="ws_plugin__s2member_eot_demotion_from" id="ws-plugin--s2member-eot-demotion-from"'.(($demotion_disabled) ? ' disabled="disabled"' : '').'>'."\n";
157 echo '<option value="s2member_level"'.(($demotion_from === 's2member_level') ? ' selected="selected"' : '').'>s2Member Level role (preserve other WordPress roles)</option>'."\n";
158 echo '<option value="all"'.(($demotion_from === 'all') ? ' selected="selected"' : '').'>All WordPress roles (legacy behavior)</option>'."\n";
159 echo '</select><br />'."\n";
160 echo '<em>These settings are used only when Membership End-of-Term Behavior is set to Demote.</em><br /><br />'."\n";
161 //260916.2004 Explain the fresh-install behavior, legacy compatibility, and capability caveat without treating Subscriber/Level 0 as a role users are demoted from.
162 echo '<em>New installations remove only the user\'s <code>s2member_levelN</code> role at EOT and preserve other WordPress roles; upgraded installations retain the legacy all-role replacement until you change this setting. Preserved roles also preserve their existing capabilities, so roles such as Editor/Author/Contributor or custom roles may continue to grant s2Member access.</em>'."\n";
163 echo '</td>'."\n";
164 echo '</tr>'."\n";
165 echo '<tr class="ws-plugin--s2member-eot-demotion-option-row"'.(($demotion_disabled) ? ' style="opacity:0.5;"' : '').'>'."\n";
166 echo '<th><label for="ws-plugin--s2member-eot-demotion-to-role">Demote To Role:</label></th>'."\n";
167 echo '</tr>'."\n";
168 echo '<tr class="ws-plugin--s2member-eot-demotion-option-row"'.(($demotion_disabled) ? ' style="opacity:0.5;"' : '').'>'."\n";
169 echo '<td>'."\n";
170 //260916.2345 Group Subscriber and s2Member Levels first, then list other registered roles by slug so larger role lists stay predictable and easier to scan.
171 global $wp_roles;
172 if(!is_object($wp_roles))
173 $wp_roles = new WP_Roles();
174 $level_roles = $other_roles = array();
175 foreach((array)$wp_roles->roles as $role_slug => $role_data)
176 {
177 if(in_array($role_slug, array('administrator', 'editor', 'author', 'contributor', 's2member_pending_deletion'), TRUE))
178 continue;
179 $role_name = !empty($role_data['name']) ? translate_user_role($role_data['name']) : $role_slug;
180 if($role_slug === 'subscriber')
181 $level_roles[0] = array($role_slug, $role_name);
182 else if(preg_match('/^s2member_level([1-9][0-9]*)$/', $role_slug, $m))
183 $level_roles[(int)$m[1]] = array($role_slug, $role_name);
184 else
185 $other_roles[$role_slug] = $role_name;
186 }
187 ksort($level_roles, SORT_NUMERIC);
188 ksort($other_roles, SORT_STRING);
189 echo '<select name="ws_plugin__s2member_eot_demotion_to_role" id="ws-plugin--s2member-eot-demotion-to-role"'.(($demotion_disabled) ? ' disabled="disabled"' : '').'>'."\n";
190 if($level_roles)
191 {
192 echo '<optgroup label="s2Member Levels">'."\n";
193 foreach($level_roles as $role)
194 echo '<option value="'.esc_attr($role[0]).'"'.(($demotion_to_role === $role[0]) ? ' selected="selected"' : '').'>'.esc_html($role[1]).' ('.esc_html($role[0]).')</option>'."\n";
195 echo '</optgroup>'."\n";
196 }
197 if($other_roles)
198 {
199 echo '<optgroup label="Other Roles">'."\n";
200 foreach($other_roles as $role_slug => $role_name)
201 echo '<option value="'.esc_attr($role_slug).'"'.(($demotion_to_role === $role_slug) ? ' selected="selected"' : '').'>'.esc_html($role_name).' ('.esc_html($role_slug).')</option>'."\n";
202 echo '</optgroup>'."\n";
203 }
204 echo '</select><br />'."\n";
205 echo '<em>Select the WordPress role to assign when s2Member demotes a member at EOT. Subscriber is the default.</em><br /><br />'."\n";
206 echo '<em>WordPress\'s elevated default roles and s2Member\'s Pending Deletion role are intentionally not available here. Any role selected here keeps the capabilities assigned to it. If those capabilities grant s2Member access, the member may still be able to access protected content after EOT.</em><br /><br />'."\n";
207 echo '<em>Developers who need a role not available here can use <code>ws_plugin__s2member_force_demotion_role</code>; see <a href="https://s2member.com/kb-article/how-do-i-specify-the-demotion-role-upon-eot/" target="_blank" rel="noopener noreferrer external">How do I specify the demotion role upon EOT?</a></em>'."\n";
208 echo '</td>'."\n";
209 echo '</tr>'."\n";
210 }
211
212 /**
213 * Adds option menus / sub-menus.
214 *
215 * @package s2Member\Menu_Pages
216 * @since 3.5
217 *
218 * @attaches-to ``add_action('admin_menu');``
219 */
220 public static function add_admin_options()
221 {
222 do_action('ws_plugin__s2member_before_add_admin_options', get_defined_vars());
223
224 add_filter('plugin_action_links', 'c_ws_plugin__s2member_menu_pages::_add_settings_link', 10, 2);
225
226 if(apply_filters('ws_plugin__s2member_during_add_admin_options_create_menu_items', TRUE, get_defined_vars()))
227 {
228 if((is_multisite() && c_ws_plugin__s2member_utils_conds::is_multisite_farm() && !is_main_site()) || apply_filters('ws_plugin__s2member_during_add_admin_options_clear_right_side', FALSE, get_defined_vars()))
229 $GLOBALS['WS_PLUGIN__']['s2member']['c']['menu_pages'] = array(); // Clear right side.
230
231 $menu = apply_filters('ws_plugin__s2member_during_add_admin_options_menu_slug', 'ws-plugin--s2member-start', get_defined_vars());
232
233 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_menu_page', TRUE, get_defined_vars()))
234 add_menu_page(((c_ws_plugin__s2member_utils_conds::pro_is_installed()) ? 's2Member (Pro)' : 's2Member'), ((c_ws_plugin__s2member_utils_conds::pro_is_installed()) ? 's2Member (Pro)' : 's2Member'),
235 'create_users', $menu, 'c_ws_plugin__s2member_menu_pages::start_page', $GLOBALS['WS_PLUGIN__']['s2member']['c']['dir_url'].'/src/images/brand-favicon.png');
236
237 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_start_page', TRUE, get_defined_vars()))
238 add_submenu_page($menu, 'Getting Started w/ s2Member', 'Getting Started', 'create_users', 'ws-plugin--s2member-start', 'c_ws_plugin__s2member_menu_pages::start_page');
239
240 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_help_page', !is_multisite () || !c_ws_plugin__s2member_utils_conds::is_multisite_farm () || is_main_site (), get_defined_vars()))
241 add_submenu_page($menu, 'Getting Help w/ s2Member', 'Getting Help', 'create_users', 'ws-plugin--s2member-help', 'c_ws_plugin__s2member_menu_pages::help_page');
242
243 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_divider_1', TRUE, get_defined_vars()))
244 add_submenu_page($menu, '', '<span style="display:block; margin:1px 0 1px -5px; padding:0; height:1px; line-height:1px; background:#CCCCCC;"></span>', 'create_users', '#');
245
246 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_mms_ops_page', (!is_multisite() || is_main_site()), get_defined_vars()))
247 add_submenu_page($menu, 's2Member Multisite Configuration', 'Multisite (Config)', 'create_users', 'ws-plugin--s2member-mms-ops', 'c_ws_plugin__s2member_menu_pages::mms_ops_page');
248
249 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_gen_ops_page', TRUE, get_defined_vars()))
250 add_submenu_page($menu, 's2Member General Options', 'General Options', 'create_users', 'ws-plugin--s2member-gen-ops', 'c_ws_plugin__s2member_menu_pages::gen_ops_page');
251
252 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_res_ops_page', TRUE, get_defined_vars()))
253 add_submenu_page($menu, 's2Member Restriction Options', 'Restriction Options', 'create_users', 'ws-plugin--s2member-res-ops', 'c_ws_plugin__s2member_menu_pages::res_ops_page');
254
255 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_down_ops_page', (!is_multisite() || !c_ws_plugin__s2member_utils_conds::is_multisite_farm() || is_main_site()), get_defined_vars()))
256 add_submenu_page($menu, 's2Member Download Options', 'Download Options', 'create_users', 'ws-plugin--s2member-down-ops', 'c_ws_plugin__s2member_menu_pages::down_ops_page');
257
258 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_divider_2', TRUE, get_defined_vars()))
259 add_submenu_page($menu, '', '<span style="display:block; margin:1px 0 1px -5px; padding:0; height:1px; line-height:1px; background:#CCCCCC;"></span>', 'create_users', '#');
260
261 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_paypal_ops_page', TRUE, get_defined_vars()))
262 add_submenu_page($menu, 's2Member PayPal Options', 'PayPal Options', 'create_users', 'ws-plugin--s2member-paypal-ops', 'c_ws_plugin__s2member_menu_pages::paypal_ops_page');
263
264 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_paypal_buttons_page', TRUE, get_defined_vars()))
265 add_submenu_page($menu, 's2Member PayPal Buttons', 'PayPal Buttons', 'create_users', 'ws-plugin--s2member-paypal-buttons', 'c_ws_plugin__s2member_menu_pages::paypal_buttons_page');
266
267 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_divider_3', TRUE, get_defined_vars()))
268 add_submenu_page($menu, '', '<span style="display:block; margin:1px 0 1px -5px; padding:0; height:1px; line-height:1px; background:#CCCCCC;"></span>', 'create_users', '#');
269
270 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_trk_ops_page', TRUE, get_defined_vars()))
271 add_submenu_page($menu, 's2Member API / Tracking', 'API / Tracking', 'create_users', 'ws-plugin--s2member-trk-ops', 'c_ws_plugin__s2member_menu_pages::trk_ops_page');
272
273 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_els_ops_page', TRUE, get_defined_vars()))
274 add_submenu_page($menu, 's2Member API / List Servers', 'API / List Servers', 'create_users', 'ws-plugin--s2member-els-ops', 'c_ws_plugin__s2member_menu_pages::els_ops_page');
275
276 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_api_ops_page', TRUE, get_defined_vars()))
277 add_submenu_page($menu, 's2Member API / Notifications', 'API / Notifications', 'create_users', 'ws-plugin--s2member-api-ops', 'c_ws_plugin__s2member_menu_pages::api_ops_page');
278
279 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_scripting_page', TRUE, get_defined_vars()))
280 add_submenu_page($menu, 's2Member API / Scripting', 'API / Scripting', 'create_users', 'ws-plugin--s2member-scripting', 'c_ws_plugin__s2member_menu_pages::scripting_page');
281
282 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_divider_4', TRUE, get_defined_vars()))
283 add_submenu_page($menu, '', '<span style="display:block; margin:1px 0 1px -5px; padding:0; height:1px; line-height:1px; background:#CCCCCC;"></span>', 'create_users', '#');
284
285 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_integrations_page', (!is_multisite() || !c_ws_plugin__s2member_utils_conds::is_multisite_farm() || is_main_site()), get_defined_vars()))
286 add_submenu_page($menu, 's2Member / Other Integrations', 'Other Integrations', 'create_users', 'ws-plugin--s2member-integrations', 'c_ws_plugin__s2member_menu_pages::integrations_page');
287
288 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_divider_5', TRUE, get_defined_vars()))
289 add_submenu_page($menu, '', '<span style="display:block; margin:1px 0 1px -5px; padding:0; height:1px; line-height:1px; background:#CCCCCC;"></span>', 'create_users', '#');
290
291 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_logs_page', (!is_multisite() || !c_ws_plugin__s2member_utils_conds::is_multisite_farm() || is_main_site()), get_defined_vars()))
292 add_submenu_page($menu, 's2Member Logs', 'Log Files (Debug)', 'create_users', 'ws-plugin--s2member-logs', 'c_ws_plugin__s2member_menu_pages::logs_page');
293
294 do_action('ws_plugin__s2member_during_add_admin_options_additional_pages', get_defined_vars());
295 }
296 do_action('ws_plugin__s2member_after_add_admin_options', get_defined_vars());
297 }
298
299 /**
300 * Adds network option menus / sub-menus.
301 *
302 * @package s2Member\Menu_Pages
303 * @since 3.5
304 *
305 * @attaches-to ``add_action('network_admin_menu');``
306 */
307 public static function add_network_admin_options()
308 {
309 do_action('ws_plugin__s2member_before_add_network_admin_options', get_defined_vars());
310
311 if(apply_filters('ws_plugin__s2member_during_add_network_admin_options_create_menu_items', TRUE, get_defined_vars()))
312 {
313 if((is_multisite() && c_ws_plugin__s2member_utils_conds::is_multisite_farm() && !is_main_site()) || apply_filters('ws_plugin__s2member_during_add_network_admin_options_clear_right_side', FALSE, get_defined_vars()))
314 $GLOBALS['WS_PLUGIN__']['s2member']['c']['menu_pages'] = array(); // Clear right side.
315
316 $menu = 'ws-plugin--s2member-mms-ops'; // Used below for nesting additional sub-menu pages.
317
318 add_menu_page('s2Member', 's2Member', 'create_users', $menu, 'c_ws_plugin__s2member_menu_pages::mms_ops_page', $GLOBALS['WS_PLUGIN__']['s2member']['c']['dir_url'].'/src/images/brand-favicon.png');
319
320 add_submenu_page($menu, 's2Member Multisite (Configuration)', 'Multisite (Config)', 'create_users', 'ws-plugin--s2member-mms-ops', 'c_ws_plugin__s2member_menu_pages::mms_ops_page');
321
322 do_action('ws_plugin__s2member_during_add_network_admin_options_additional_pages', get_defined_vars());
323 }
324 do_action('ws_plugin__s2member_after_add_network_admin_options', get_defined_vars());
325 }
326
327 /**
328 * A sort of callback function to add the settings link.
329 *
330 * @package s2Member\Menu_Pages
331 * @since 3.5
332 *
333 * @attaches-to ``add_filter('plugin_action_links');``
334 *
335 * @param array $actions Expects an existing array of actions links, passed in by the Filter.
336 * @param string $plugin_file Expects path to a plugin file. We need to test against this for s2Member.
337 *
338 * @return array An array of links, Filtered by this routine.
339 */
340 public static function _add_settings_link($actions = array(), $plugin_file = '')
341 {
342 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
343 do_action('_ws_plugin__s2member_before_add_settings_link', get_defined_vars());
344 unset($__refs, $__v); // Housekeeping.
345
346 if($plugin_file === $GLOBALS['WS_PLUGIN__']['s2member']['c']['plugin_basename'] && is_array($actions))
347 {
348 $settings = '<a href="'.esc_attr(admin_url('/admin.php?page=ws-plugin--s2member-gen-ops')).'">Settings</a>';
349 array_unshift($actions, apply_filters('ws_plugin__s2member_add_settings_link', $settings, get_defined_vars()));
350
351 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
352 do_action('_ws_plugin__s2member_during_add_settings_link', get_defined_vars());
353 unset($__refs, $__v); // Housekeeping.
354 }
355 return apply_filters('_ws_plugin__s2member_add_settings_link', $actions, get_defined_vars());
356 }
357
358 /**
359 * Enqueue scripts for administrative menu pages.
360 *
361 * @package s2Member\Menu_Pages
362 * @since 3.5
363 *
364 * @attaches-to ``add_action('admin_print_scripts');``
365 */
366 public static function add_admin_scripts()
367 {
368 do_action('ws_plugin__s2member_before_add_admin_scripts', get_defined_vars());
369
370 if(!empty($_GET['page']) && preg_match('/ws-plugin--s2member-/', $_GET['page']))
371 {
372 wp_enqueue_script('jquery');
373 wp_enqueue_script('thickbox');
374 wp_enqueue_script('media-upload');
375 wp_enqueue_script('jquery-ui-core');
376 wp_enqueue_script('jquery-sprintf', $GLOBALS['WS_PLUGIN__']['s2member']['c']['dir_url'].'/src/includes/jquery/jquery.sprintf/jquery.sprintf.min.js', array('jquery'), c_ws_plugin__s2member_utilities::ver_checksum());
377 wp_enqueue_script('jquery-json-ps', $GLOBALS['WS_PLUGIN__']['s2member']['c']['dir_url'].'/src/includes/jquery/jquery.json-ps/jquery.json-ps.min.js', array('jquery'), c_ws_plugin__s2member_utilities::ver_checksum());
378 wp_enqueue_script('jquery-ui-effects', $GLOBALS['WS_PLUGIN__']['s2member']['c']['dir_url'].'/src/includes/jquery/jquery.ui-effects/jquery.ui-effects.min.js', array('jquery', 'jquery-ui-core'), c_ws_plugin__s2member_utilities::ver_checksum());
379 wp_enqueue_script('ws-plugin--s2member-menu-pages', admin_url('admin.php?ws_plugin__s2member_menu_pages_js='.urlencode(mt_rand()), is_ssl() ? 'https' : 'http'), array('jquery', 'thickbox', 'media-upload', 'jquery-sprintf', 'jquery-json-ps', 'jquery-ui-core', 'jquery-ui-effects', 'password-strength-meter'), c_ws_plugin__s2member_utilities::ver_checksum());
380
381 do_action('ws_plugin__s2member_during_add_admin_scripts', get_defined_vars());
382 }
383 do_action('ws_plugin__s2member_after_add_admin_scripts', get_defined_vars());
384 }
385
386 /**
387 * Enqueue styles for administrative menu pages.
388 *
389 * @package s2Member\Menu_Pages
390 * @since 3.5
391 *
392 * @attaches-to ``add_action('admin_print_styles');``
393 */
394 public static function add_admin_styles()
395 {
396 do_action('ws_plugin__s2member_before_add_admin_styles', get_defined_vars());
397
398 if(!empty($_GET['page']) && preg_match('/ws-plugin--s2member-/', $_GET['page']))
399 {
400 wp_enqueue_style('thickbox');
401 wp_enqueue_style('ws-plugin--s2member-menu-pages', admin_url('admin.php?ws_plugin__s2member_menu_pages_css='.urlencode(mt_rand()), is_ssl() ? 'https' : 'http'), array('thickbox'), c_ws_plugin__s2member_utilities::ver_checksum(), 'all');
402
403 do_action('ws_plugin__s2member_during_add_admin_styles', get_defined_vars());
404 }
405 do_action('ws_plugin__s2member_after_add_admin_styles', get_defined_vars());
406 }
407
408 /**
409 * Handles log file downloads.
410 *
411 * @package s2Member\Menu_Pages
412 * @since 120310
413 */
414 public static function log_file_downloader()
415 {
416 if(!current_user_can('create_users')) return;
417 if(is_multisite() && c_ws_plugin__s2member_utils_conds::is_multisite_farm() && !is_main_site())
418 return; // We do NOT provide this functionality on Child Blogs of a Blog Farm Network.
419
420 if(!empty($_GET['ws_plugin__s2member_download_log_file']) && is_string($log_file = $_GET['ws_plugin__s2member_download_log_file']) && strpos($log_file, '..') === FALSE && strpos(basename($log_file), '.') !== 0)
421 if(!empty($_GET['ws_plugin__s2member_download_log_file_v']) && is_string($nonce = $_GET['ws_plugin__s2member_download_log_file_v']) && wp_verify_nonce($nonce, 'ws-plugin--s2member-download-log-file-v'))
422 {
423 $logs_dir = $GLOBALS['WS_PLUGIN__']['s2member']['c']['logs_dir'];
424
425 if(file_exists($logs_dir.'/'.$log_file))
426 $log_file_contents = file_get_contents($logs_dir.'/'.$log_file);
427 else $log_file_contents = '';
428
429 @set_time_limit(0);
430 @ini_set('memory_limit', apply_filters('admin_memory_limit', WP_MAX_MEMORY_LIMIT));
431
432 @ini_set('zlib.output_compression', 0);
433 if(function_exists('apache_setenv'))
434 @apache_setenv('no-gzip', '1');
435
436 while(@ob_end_clean()) ;
437
438 status_header(200); // 200 OK status header.
439
440 header('Content-Encoding: none');
441 header('Accept-Ranges: none');
442 header('Content-Type: text/plain; charset=UTF-8');
443 header('Content-Length: '.strlen($log_file_contents));
444 header('Expires: '.gmdate('D, d M Y H:i:s', strtotime('-1 week')).' GMT');
445 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
446 header('Cache-Control: no-cache, must-revalidate, max-age=0');
447 header('Cache-Control: post-check=0, pre-check=0', FALSE);
448 header('Pragma: no-cache');
449
450 header('Content-Disposition: attachment; filename="'.$log_file.'"');
451
452 exit($log_file_contents); // Log file.
453 }
454 }
455
456 /**
457 * Handles log file downloads (in ZIP format).
458 *
459 * @package s2Member\Menu_Pages
460 * @since 120310
461 */
462 public static function logs_zip_downloader()
463 {
464 if(!current_user_can('create_users')) return;
465 if(is_multisite() && c_ws_plugin__s2member_utils_conds::is_multisite_farm() && !is_main_site())
466 return; // We do NOT provide this functionality on Child Blogs of a Blog Farm Network.
467
468 if(!empty($_POST['ws_plugin__s2member_logs_download_zip']) && is_string($nonce = $_POST['ws_plugin__s2member_logs_download_zip']) && wp_verify_nonce($nonce, 'ws-plugin--s2member-logs-download-zip'))
469 {
470 $logs_dir = $GLOBALS['WS_PLUGIN__']['s2member']['c']['logs_dir'];
471 $s2member_logs_zip = $logs_dir.'/'.$_SERVER['HTTP_HOST'].'--s2member-logs.zip';
472
473 if(is_dir($logs_dir)) // Do we have a logs directory?
474 {
475 include_once ABSPATH.'wp-admin/includes/class-pclzip.php';
476
477 if(file_exists($s2member_logs_zip) && is_writable($s2member_logs_zip))
478 unlink($s2member_logs_zip);
479
480 $archive = new PclZip($s2member_logs_zip);
481 $archive->create($logs_dir, PCLZIP_OPT_REMOVE_ALL_PATH);
482 }
483 if(file_exists($s2member_logs_zip))
484 $s2member_logs_zip_size = filesize($s2member_logs_zip);
485 else $s2member_logs_zip_size = 0;
486
487 @set_time_limit(0);
488 @ini_set('memory_limit', apply_filters('admin_memory_limit', WP_MAX_MEMORY_LIMIT));
489
490 @ini_set('zlib.output_compression', 0);
491 if(function_exists('apache_setenv'))
492 @apache_setenv('no-gzip', '1');
493
494 while(@ob_end_clean()) ;
495
496 status_header(200); // 200 OK status header.
497
498 header('Content-Encoding: none');
499 header('Accept-Ranges: none');
500 header('Content-Type: application/zip');
501 header('Content-Length: '.$s2member_logs_zip_size);
502 header('Expires: '.gmdate('D, d M Y H:i:s', strtotime('-1 week')).' GMT');
503 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
504 header('Cache-Control: no-cache, must-revalidate, max-age=0');
505 header('Cache-Control: post-check=0, pre-check=0', FALSE);
506 header('Pragma: no-cache');
507
508 header('Content-Disposition: attachment; filename="'.basename($s2member_logs_zip).'"');
509
510 if($s2member_logs_zip_size && is_resource($resource = fopen($s2member_logs_zip, 'rb')))
511 {
512 $_bytes_to_read = $s2member_logs_zip_size; // Total bytes we need to read for this file.
513
514 $chunk_size = apply_filters('ws_plugin__s2member_file_downloads_chunk_size', 2097152, get_defined_vars());
515
516 while($_bytes_to_read) // We have bytes to read here.
517 {
518 $_bytes_to_read -= ($_reading = ($_bytes_to_read > $chunk_size) ? $chunk_size : $_bytes_to_read);
519 echo fread($resource, $_reading); // Serve file in chunks (default chunk size is 2MB).
520 flush(); // Flush each chunk to the browser as it is served (avoids high memory consumption).
521 }
522 fclose($resource); // Close file resource handle.
523 unset($_bytes_to_read, $_reading); // Housekeeping.
524 }
525 exit; // Clean exit after serving file.
526 }
527 }
528
529 /**
530 * Archives existing log files and starts fresh with new logs.
531 *
532 * @package s2Member\Menu_Pages
533 * @since 120310
534 */
535 public static function archive_logs_start_fresh()
536 {
537 if(!current_user_can('create_users')) return;
538 if(is_multisite() && c_ws_plugin__s2member_utils_conds::is_multisite_farm() && !is_main_site())
539 return; // We do NOT provide this functionality on Child Blogs of a Blog Farm Network.
540
541 if(!empty($_POST['ws_plugin__s2member_logs_archive_start_fresh']) && is_string($nonce = $_POST['ws_plugin__s2member_logs_archive_start_fresh']) && wp_verify_nonce($nonce, 'ws-plugin--s2member-logs-archive-start-fresh'))
542 {
543 if(is_dir($logs_dir = $GLOBALS['WS_PLUGIN__']['s2member']['c']['logs_dir']))
544 foreach(scandir($logs_dir) as $log_file) // Archive existing log files here.
545 {
546 if(preg_match('/\.log$/', $log_file) && stripos($log_file, '-ARCHIVED-') === FALSE)
547 if(is_file($log_dir_file = $logs_dir.'/'.$log_file) && is_writable($log_dir_file))
548 if(!rename($log_dir_file, preg_replace('/\.log$/i', '', $log_dir_file).'-ARCHIVED-'.date('m-d-Y').'-'.time().'.log'))
549 $error = TRUE;
550 }
551 if(!empty($error))
552 c_ws_plugin__s2member_admin_notices::display_admin_notice('Unknown error when attempting to archive log files. Please check directory permissions.', TRUE);
553 else c_ws_plugin__s2member_admin_notices::display_admin_notice('All log files have been archived succesfully.');
554 }
555 }
556
557 /**
558 * Deletes existing log files and starts fresh with new logs.
559 *
560 * @package s2Member\Menu_Pages
561 * @since 120312
562 */
563 public static function delete_logs_start_fresh()
564 {
565 if(!current_user_can('create_users')) return;
566 if(is_multisite() && c_ws_plugin__s2member_utils_conds::is_multisite_farm() && !is_main_site())
567 return; // We do NOT provide this functionality on Child Blogs of a Blog Farm Network.
568
569 if(!empty($_POST['ws_plugin__s2member_logs_delete_start_fresh']) && is_string($nonce = $_POST['ws_plugin__s2member_logs_delete_start_fresh']) && wp_verify_nonce($nonce, 'ws-plugin--s2member-logs-delete-start-fresh'))
570 {
571 if(is_dir($logs_dir = $GLOBALS['WS_PLUGIN__']['s2member']['c']['logs_dir']))
572 foreach(scandir($logs_dir) as $log_file) // Delete existing log files here.
573 {
574 if(preg_match('/\.log$/', $log_file))
575 if(is_file($log_dir_file = $logs_dir.'/'.$log_file) && is_writable($log_dir_file))
576 if(!unlink($log_dir_file)) $error = TRUE;
577 }
578 if(!empty($error))
579 c_ws_plugin__s2member_admin_notices::display_admin_notice('Unknown error when attempting to delete log files. Please check directory permissions.', TRUE);
580 else c_ws_plugin__s2member_admin_notices::display_admin_notice('All log files have been deleted succesfully.');
581 }
582 }
583
584 /**
585 * Builds and handles the Getting Started page.
586 *
587 * @package s2Member\Menu_Pages
588 * @since 3.5
589 */
590 public static function start_page()
591 {
592 do_action('ws_plugin__s2member_before_start_page', get_defined_vars());
593
594 include_once dirname(dirname(__FILE__)).'/menu-pages/start.inc.php';
595
596 do_action('ws_plugin__s2member_after_start_page', get_defined_vars());
597 }
598
599 /**
600 * Builds and handles the Getting Help page.
601 *
602 * @package s2Member\Menu_Pages
603 * @since 151218
604 */
605 public static function help_page()
606 {
607 do_action('ws_plugin__s2member_before_help_page', get_defined_vars());
608
609 include_once dirname(dirname(__FILE__)).'/menu-pages/help.inc.php';
610
611 do_action('ws_plugin__s2member_after_help_page', get_defined_vars());
612 }
613
614 /**
615 * Builds and handles the Main Multisite Options page.
616 *
617 * @package s2Member\Menu_Pages
618 * @since 3.5
619 */
620 public static function mms_ops_page()
621 {
622 do_action('ws_plugin__s2member_before_mms_ops_page', get_defined_vars());
623
624 include_once dirname(dirname(__FILE__)).'/menu-pages/mms-ops.inc.php';
625
626 do_action('ws_plugin__s2member_after_mms_ops_page', get_defined_vars());
627 }
628
629 /**
630 * Builds and handles the General Options page.
631 *
632 * @package s2Member\Menu_Pages
633 * @since 3.5
634 */
635 public static function gen_ops_page()
636 {
637 do_action('ws_plugin__s2member_before_gen_ops_page', get_defined_vars());
638
639 c_ws_plugin__s2member_menu_pages::update_all_options();
640
641 include_once dirname(dirname(__FILE__)).'/menu-pages/gen-ops.inc.php';
642
643 do_action('ws_plugin__s2member_after_gen_ops_page', get_defined_vars());
644 }
645
646 /**
647 * Builds and handles the Restriction Options page.
648 *
649 * @package s2Member\Menu_Pages
650 * @since 3.5
651 */
652 public static function res_ops_page()
653 {
654 do_action('ws_plugin__s2member_before_res_ops_page', get_defined_vars());
655
656 c_ws_plugin__s2member_menu_pages::update_all_options();
657
658 include_once dirname(dirname(__FILE__)).'/menu-pages/res-ops.inc.php';
659
660 do_action('ws_plugin__s2member_after_res_ops_page', get_defined_vars());
661 }
662
663 /**
664 * Builds and handles the Paypal Options page.
665 *
666 * @package s2Member\Menu_Pages
667 * @since 3.5
668 */
669 public static function paypal_ops_page()
670 {
671 do_action('ws_plugin__s2member_before_paypal_ops_page', get_defined_vars());
672
673 c_ws_plugin__s2member_menu_pages::update_all_options();
674
675 $logs_dir = $GLOBALS['WS_PLUGIN__']['s2member']['c']['logs_dir'];
676
677 if(!is_dir($logs_dir) && is_writable(dirname(c_ws_plugin__s2member_utils_dirs::strip_dir_app_data($logs_dir))))
678 mkdir($logs_dir, 0777, TRUE).clearstatcache();
679
680 $htaccess = $GLOBALS['WS_PLUGIN__']['s2member']['c']['logs_dir'].'/.htaccess';
681 $htaccess_contents = trim(c_ws_plugin__s2member_utilities::evl(file_get_contents($GLOBALS['WS_PLUGIN__']['s2member']['c']['logs_dir_htaccess'])));
682
683 if(is_dir($logs_dir) && is_writable($logs_dir) && !file_exists($htaccess))
684 file_put_contents($htaccess, $htaccess_contents).clearstatcache();
685
686 if($GLOBALS['WS_PLUGIN__']['s2member']['o']['gateway_debug_logs']) // Logging enabled?
687 {
688 if(!is_dir($logs_dir)) // If the security-enabled logs directory does not exist yet.
689 c_ws_plugin__s2member_admin_notices::display_admin_notice('The security-enabled logs directory (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($logs_dir)).'</code>) does not exist. Please create this directory manually &amp; make it writable (chmod 777).', TRUE);
690
691 else if(!is_writable($logs_dir)) // If the logs directory is not writable yet.
692 c_ws_plugin__s2member_admin_notices::display_admin_notice('Permissions error. The security-enabled logs directory (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($logs_dir)).'</code>) is not writable. Please make this directory writable (chmod 777).', TRUE);
693
694 if(!file_exists($htaccess)) // If the .htaccess file has not been created yet.
695 c_ws_plugin__s2member_admin_notices::display_admin_notice('The .htaccess protection file (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($htaccess)).'</code>) does not exist. Please create this file manually. Inside your .htaccess file, add this:<br /><pre>'.esc_html($htaccess_contents).'</pre>', TRUE);
696
697 else if(!preg_match('/deny from all/i', file_get_contents($htaccess))) // Else if the .htaccess file does not offer the required protection.
698 c_ws_plugin__s2member_admin_notices::display_admin_notice('Unprotected. The .htaccess protection file (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($htaccess)).'</code>) does not contain <code>deny from all</code>. Inside your .htaccess file, add this:<br /><pre>'.esc_html($htaccess_contents).'</pre>', TRUE);
699 }
700 include_once dirname(dirname(__FILE__)).'/menu-pages/paypal-ops.inc.php';
701
702 do_action('ws_plugin__s2member_after_paypal_ops_page', get_defined_vars());
703 }
704
705 /**
706 * Builds and handles the Download Options page.
707 *
708 * @package s2Member\Menu_Pages
709 * @since 3.5
710 */
711 public static function down_ops_page()
712 {
713 do_action('ws_plugin__s2member_before_down_ops_page', get_defined_vars());
714
715 c_ws_plugin__s2member_menu_pages::update_all_options();
716
717 if(!empty($_REQUEST['ws_plugin__s2member_cf_options_reset'])
718 && wp_verify_nonce($_REQUEST['ws_plugin__s2member_cf_options_reset'], 'ws-plugin--s2member-cf-options-reset')
719 )
720 {
721 c_ws_plugin__s2member_files_in::reset_aws_cf_config_values(); // A full CloudFront reset.
722 c_ws_plugin__s2member_admin_notices::display_admin_notice('Amazon CloudFront configuration reset successfully.');
723 }
724 $files_dir = $GLOBALS['WS_PLUGIN__']['s2member']['c']['files_dir'];
725
726 $htaccess = $GLOBALS['WS_PLUGIN__']['s2member']['c']['files_dir'].'/.htaccess';
727 $htaccess_contents = trim(c_ws_plugin__s2member_utilities::evl(file_get_contents($GLOBALS['WS_PLUGIN__']['s2member']['c']['files_dir_htaccess'])));
728
729 $no_gzip_htaccess = ABSPATH.'.htaccess'; // Always located in the absolute root path for WordPress.
730 $no_gzip_htaccess_contents = trim(c_ws_plugin__s2member_utilities::evl(file_get_contents($GLOBALS['WS_PLUGIN__']['s2member']['c']['files_no_gzip_htaccess'])));
731
732 if(!c_ws_plugin__s2member_files::no_gzip_rules_in_root_htaccess()) // If s2Member's GZIP exclusions do NOT yet exist in the root `.htaccess` file.
733 c_ws_plugin__s2member_files::write_no_gzip_into_root_htaccess().clearstatcache(); // Handle the root `.htaccess` file now.
734
735 if(!is_dir($files_dir) && is_writable(dirname(c_ws_plugin__s2member_utils_dirs::strip_dir_app_data($files_dir))))
736 mkdir($files_dir, 0777, TRUE).clearstatcache(); // Create this directory structure now.
737
738 if(is_dir($files_dir) && is_writable($files_dir) && !file_exists($htaccess)) // This file does NOT exist yet?
739 file_put_contents($htaccess, $htaccess_contents).clearstatcache(); // Create the `.htaccess` file now.
740
741 if(!c_ws_plugin__s2member_files::no_gzip_rules_in_root_htaccess()) // If s2Member's GZIP exclusions do NOT yet exist in the root `.htaccess` file.
742 c_ws_plugin__s2member_admin_notices::display_admin_notice('Possible GZIP conflict on server. Unable to write GZIP exclusions into root .htaccess file (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($no_gzip_htaccess)).'</code>). Please read the panel below: <strong>Preventing GZIP Conflicts</strong>, and add this section yourself:<br /><pre>'.esc_html($no_gzip_htaccess_contents).'</pre>', TRUE);
743
744 if(!is_dir($files_dir)) // If the security-enabled files directory does not exist yet.
745 c_ws_plugin__s2member_admin_notices::display_admin_notice('The security-enabled files directory (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($files_dir)).'</code>) does not exist. Please create this directory manually.', TRUE);
746
747 if(!file_exists($htaccess)) // If the `.htaccess` file has not been created yet.
748 c_ws_plugin__s2member_admin_notices::display_admin_notice('The .htaccess protection file (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($htaccess)).'</code>) does not exist. Please create this file manually. Inside your .htaccess file, add this:<br /><pre>'.esc_html($htaccess_contents).'</pre>', TRUE);
749
750 else if(!preg_match('/deny from all/i', file_get_contents($htaccess))) // Else if the `.htaccess` file does not offer the required protection.
751 c_ws_plugin__s2member_admin_notices::display_admin_notice('Unprotected. The .htaccess protection file (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($htaccess)).'</code>) does not contain <code>deny from all</code>. Inside your .htaccess file, add this:<br /><pre>'.esc_html($htaccess_contents).'</pre>', TRUE);
752
753 if(!empty($_POST['ws_plugin__s2member_amazon_cf_files_auto_configure_distros']) && ($nonce = $_POST['ws_plugin__s2member_amazon_cf_files_auto_configure_distros']) && wp_verify_nonce($nonce, 'ws-plugin--s2member-amazon-cf-files-auto-configure-distros'))
754 if(($amazon_cf_auto_configure_distros = c_ws_plugin__s2member_files_in::amazon_cf_auto_configure_distros()) && $amazon_cf_auto_configure_distros['success'])
755 c_ws_plugin__s2member_admin_notices::display_admin_notice('Amazon CloudFront Distributions auto-configured successfully. Please allow 30 minutes for initial propagation. <strong>Tip:</strong> If you try to stream over the RTMP protocol using something like the <code>[s2Stream /]</code> shortcode, and you keep getting an "ID Not Found" error while using JW Player; please note that it can <em>sometimes</em> take a full 24 hours for RTMP (i.e., streaming distributions) to begin working properly. This is because there are a few initialization routines that must complete on the AWS side when you first integrate with CloudFront. Please be patient.'.(($GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_cf_files_distro_downloads_cname']) ? '<br /><em>Downloads Distribution CNAME: <code>'.esc_html($GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_cf_files_distro_downloads_cname']).' &mdash;&raquo; '.esc_html($GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_cf_files_distro_downloads_dname']).'</code></em>' : '').(($GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_cf_files_distro_streaming_cname']) ? '<br /><em>Streaming Distribution CNAME: <code>'.esc_html($GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_cf_files_distro_streaming_cname']).' &mdash;&raquo; '.esc_html($GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_cf_files_distro_streaming_dname']).'</code></em>' : ''));
756 else // Else there was an error. We need to report this back to the site owner so they can understand what's going on.
757 (c_ws_plugin__s2member_menu_pages::$pre_display_errors['cf_files_auto_configure_distros'] = TRUE).c_ws_plugin__s2member_admin_notices::display_admin_notice('Unable to auto-configure Amazon CloudFront Distributions.<br />Error code: <code>'.esc_html($amazon_cf_auto_configure_distros['code']).'</code>. Error Message: <code>'.esc_html($amazon_cf_auto_configure_distros['message']).'</code>', TRUE);
758
759 if(!empty($_POST['ws_plugin__s2member_amazon_s3_files_auto_configure_acls']) && ($nonce = $_POST['ws_plugin__s2member_amazon_s3_files_auto_configure_acls']) && wp_verify_nonce($nonce, 'ws-plugin--s2member-amazon-s3-files-auto-configure-acls'))
760 if(($amazon_s3_auto_configure_acls = c_ws_plugin__s2member_files_in::amazon_s3_auto_configure_acls()) && $amazon_s3_auto_configure_acls['success'])
761 c_ws_plugin__s2member_admin_notices::display_admin_notice('Amazon S3 ACLs auto-configured successfully.');
762 else // Else there was an error. We need to report this back to the site owner so they can understand what's going on.
763 (c_ws_plugin__s2member_menu_pages::$pre_display_errors['s3_files_auto_configure_acls'] = TRUE).c_ws_plugin__s2member_admin_notices::display_admin_notice('Unable to auto-configure Amazon S3 ACLs.<br />Error code: <code>'.esc_html($amazon_s3_auto_configure_acls['code']).'</code>. Error Message: <code>'.esc_html($amazon_s3_auto_configure_acls['message']).'</code>', TRUE);
764
765 include_once dirname(dirname(__FILE__)).'/menu-pages/down-ops.inc.php';
766
767 do_action('ws_plugin__s2member_after_down_ops_page', get_defined_vars());
768 }
769
770 /**
771 * Builds and handles the API Tracking options page.
772 *
773 * @package s2Member\Menu_Pages
774 * @since 3.5
775 */
776 public static function trk_ops_page()
777 {
778 do_action('ws_plugin__s2member_before_trk_ops_page', get_defined_vars());
779
780 c_ws_plugin__s2member_menu_pages::update_all_options();
781
782 include_once dirname(dirname(__FILE__)).'/menu-pages/trk-ops.inc.php';
783
784 do_action('ws_plugin__s2member_after_trk_ops_page', get_defined_vars());
785 }
786
787 /**
788 * Builds and handles the API List Server options page.
789 *
790 * @package s2Member\Menu_Pages
791 * @since 3.5
792 */
793 public static function els_ops_page()
794 {
795 do_action('ws_plugin__s2member_before_els_ops_page', get_defined_vars());
796
797 c_ws_plugin__s2member_menu_pages::update_all_options();
798
799 include_once dirname(dirname(__FILE__)).'/menu-pages/els-ops.inc.php';
800
801 do_action('ws_plugin__s2member_after_els_ops_page', get_defined_vars());
802 }
803
804 /**
805 * Builds and handles the API Notifications page.
806 *
807 * @package s2Member\Menu_Pages
808 * @since 3.5
809 */
810 public static function api_ops_page()
811 {
812 do_action('ws_plugin__s2member_before_api_ops_page', get_defined_vars());
813
814 c_ws_plugin__s2member_menu_pages::update_all_options();
815
816 include_once dirname(dirname(__FILE__)).'/menu-pages/api-ops.inc.php';
817
818 do_action('ws_plugin__s2member_after_api_ops_page', get_defined_vars());
819 }
820
821 /**
822 * Builds and handles the PayPal Button Generator page.
823 *
824 * @package s2Member\Menu_Pages
825 * @since 3.5
826 */
827 public static function paypal_buttons_page()
828 {
829 do_action('ws_plugin__s2member_before_paypal_buttons_page', get_defined_vars());
830
831 if(!$GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_business'] || !$GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_merchant_id'] || !$GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_api_username'] || !$GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_api_password'] || !$GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_api_signature'])
832 c_ws_plugin__s2member_admin_notices::display_admin_notice('Please configure <strong>s2Member → PayPal Options</strong> first. Once all of your PayPal Options are configured; including your Email Address, Merchant ID, API Username, Password, and Signature; return to this page &amp; generate your PayPal Button(s).', TRUE);
833
834 include_once dirname(dirname(__FILE__)).'/menu-pages/paypal-buttons.inc.php';
835
836 do_action('ws_plugin__s2member_after_paypal_buttons_page', get_defined_vars());
837 }
838
839 /**
840 * Builds and handles the API Scripting page.
841 *
842 * @package s2Member\Menu_Pages
843 * @since 3.5
844 */
845 public static function scripting_page()
846 {
847 do_action('ws_plugin__s2member_before_scripting_page', get_defined_vars());
848
849 c_ws_plugin__s2member_menu_pages::update_all_options();
850
851 include_once dirname(dirname(__FILE__)).'/menu-pages/scripting.inc.php';
852
853 do_action('ws_plugin__s2member_after_scripting_page', get_defined_vars());
854 }
855
856 /**
857 * Builds and handles the Integrations page.
858 *
859 * @package s2Member\Menu_Pages
860 * @since 3.5
861 */
862 public static function integrations_page()
863 {
864 do_action('ws_plugin__s2member_before_integrations_page', get_defined_vars());
865
866 include_once dirname(dirname(__FILE__)).'/menu-pages/integrations.inc.php';
867
868 do_action('ws_plugin__s2member_after_integrations_page', get_defined_vars());
869 }
870
871 /**
872 * Builds and handles the Logs page.
873 *
874 * @package s2Member\Menu_Pages
875 * @since 120310
876 */
877 public static function logs_page()
878 {
879 do_action('ws_plugin__s2member_before_logs_page', get_defined_vars());
880
881 c_ws_plugin__s2member_menu_pages::update_all_options();
882 c_ws_plugin__s2member_menu_pages::archive_logs_start_fresh();
883 c_ws_plugin__s2member_menu_pages::delete_logs_start_fresh();
884
885 $logs_dir = $GLOBALS['WS_PLUGIN__']['s2member']['c']['logs_dir'];
886
887 if(!is_dir($logs_dir) && is_writable(dirname(c_ws_plugin__s2member_utils_dirs::strip_dir_app_data($logs_dir))))
888 mkdir($logs_dir, 0777, TRUE).clearstatcache();
889
890 $htaccess = $GLOBALS['WS_PLUGIN__']['s2member']['c']['logs_dir'].'/.htaccess';
891 $htaccess_contents = trim(c_ws_plugin__s2member_utilities::evl(file_get_contents($GLOBALS['WS_PLUGIN__']['s2member']['c']['logs_dir_htaccess'])));
892
893 if(is_dir($logs_dir) && is_writable($logs_dir) && !file_exists($htaccess))
894 file_put_contents($htaccess, $htaccess_contents).clearstatcache();
895
896 if(!is_dir($logs_dir)) // If the security-enabled logs directory does not exist yet.
897 c_ws_plugin__s2member_admin_notices::display_admin_notice('The security-enabled logs directory (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($logs_dir)).'</code>) does not exist. Please create this directory manually &amp; make it writable (chmod 777).', TRUE);
898
899 else if(!is_writable($logs_dir)) // If the logs directory is not writable yet.
900 c_ws_plugin__s2member_admin_notices::display_admin_notice('Permissions error. The security-enabled logs directory (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($logs_dir)).'</code>) is not writable. Please make this directory writable (chmod 777).', TRUE);
901
902 if(!file_exists($htaccess)) // If the .htaccess file has not been created yet.
903 c_ws_plugin__s2member_admin_notices::display_admin_notice('The .htaccess protection file (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($htaccess)).'</code>) does not exist. Please create this file manually. Inside your .htaccess file, add this:<br /><pre>'.esc_html($htaccess_contents).'</pre>', TRUE);
904
905 else if(!preg_match('/deny from all/i', file_get_contents($htaccess))) // Else if the .htaccess file does not offer the required protection.
906 c_ws_plugin__s2member_admin_notices::display_admin_notice('Unprotected. The .htaccess protection file (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($htaccess)).'</code>) does not contain <code>deny from all</code>. Inside your .htaccess file, add this:<br /><pre>'.esc_html($htaccess_contents).'</pre>', TRUE);
907
908 if(!$GLOBALS['WS_PLUGIN__']['s2member']['o']['gateway_debug_logs']) // Logging disabled?
909 c_ws_plugin__s2member_admin_notices::display_admin_notice('Logging is currently disabled by your configuration.');
910
911 include_once dirname(dirname(__FILE__)).'/menu-pages/logs.inc.php';
912
913 do_action('ws_plugin__s2member_after_logs_page', get_defined_vars());
914 }
915
916 /**
917 * Registers the Get Help panel across admin pages.
918 *
919 * @package s2Member\Menu_Pages
920 * @since 250824
921 *
922 * @attaches-to ``add_action('admin_menu');``
923 */
924 public static function get_help_panel() {
925 $hooks = array(
926 // Framework
927 'ws_plugin__s2member_during_api_ops_page_before_left_sections_form',
928 'ws_plugin__s2member_during_down_ops_page_before_left_sections_form',
929 'ws_plugin__s2member_during_els_ops_page_before_left_sections_form',
930 'ws_plugin__s2member_during_gen_ops_page_before_left_sections_form',
931 'ws_plugin__s2member_during_help_page_before_left_sections',
932 'ws_plugin__s2member_during_integrations_page_before_left_sections',
933 'ws_plugin__s2member_during_logs_page_before_left_sections',
934 'ws_plugin__s2member_during_paypal_buttons_page_before_left_sections',
935 'ws_plugin__s2member_during_paypal_ops_page_before_left_sections_form',
936 'ws_plugin__s2member_during_res_ops_page_before_left_sections_form',
937 'ws_plugin__s2member_during_scripting_page_before_left_sections',
938 'ws_plugin__s2member_during_start_page_before_left_sections',
939 'ws_plugin__s2member_during_trk_ops_page_before_left_sections_form',
940 // Pro
941 'ws_plugin__s2member_during_authnet_forms_page_before_left_sections',
942 'ws_plugin__s2member_during_authnet_ops_page_before_left_sections_form',
943 'ws_plugin__s2member_during_clickbank_buttons_page_before_left_sections',
944 'ws_plugin__s2member_during_clickbank_ops_page_before_left_sections_form',
945 'ws_plugin__s2member_during_coupon_codes_page_before_left_sections_form',
946 'ws_plugin__s2member_during_import_export_page_before_left_sections',
947 'ws_plugin__s2member_during_mms_ops_page_before_left_sections_form',
948 'ws_plugin__s2member_during_other_gateways_page_before_left_sections_form',
949 'ws_plugin__s2member_during_paypal_forms_page_before_left_sections',
950 'ws_plugin__s2member_during_stripe_forms_page_before_left_sections',
951 'ws_plugin__s2member_during_stripe_ops_page_before_left_sections_form',
952 );
953
954 foreach ($hooks as $hook) {
955 add_action($hook, 'c_ws_plugin__s2member_menu_pages::get_help_panel_load', 1, 1);
956 }
957 }
958
959 /**
960 * Renders the Get Help panel (computes $notice/$draft and includes template).
961 *
962 * @package s2Member\Menu_Pages
963 * @since 250824
964 *
965 * @param array $vars Optional vars passed by the hook (unused).
966 */
967 public static function get_help_panel_load($vars = array()) {
968 if (!current_user_can('manage_options')) return;
969
970 // Build notice (if POST).
971 $notice = array('type' => '', 'text' => '');
972 if (!empty($_POST['s2m_help_nonce']) && isset($_POST['s2m_help_submit']) && wp_verify_nonce($_POST['s2m_help_nonce'], 's2member_get_help')) {
973 $result = c_ws_plugin__s2member_menu_pages::get_help_mail();
974 if (!empty($result['ok'])) {
975 $notice = array('type' => 'success', 'text' => __('Thanks — your message was sent. You’ll receive a copy by email.', 's2member'));
976 $_POST = array(); // clear fields after success so defaults repopulate
977 } else {
978 $msg = isset($result['msg']) ? $result['msg'] : __('Sorry, your message could not be sent. Please try again.', 's2member');
979 $notice = array('type' => 'error', 'text' => $msg);
980 }
981 }
982
983 // Build draft (preserve POST on error; else defaults from current user).
984 $current = wp_get_current_user();
985 $draft = array(
986 'name' => is_object($current) ? trim($current->display_name) : '',
987 'email' => is_object($current) ? trim($current->user_email) : '',
988 'subject' => '',
989 'details' => '',
990 'meta' => home_url()."\n"
991 .'PHP v'.PHP_VERSION
992 .', WordPress v'.(isset($GLOBALS['wp_version']) ? $GLOBALS['wp_version'] : '')
993 .', s2Member v'.(defined('WS_PLUGIN__S2MEMBER_VERSION') ? WS_PLUGIN__S2MEMBER_VERSION : '')
994 .(defined('WS_PLUGIN__S2MEMBER_PRO_VERSION') ? ', Pro v'.WS_PLUGIN__S2MEMBER_PRO_VERSION : ''),
995 );
996
997 if (isset($_POST['name'])) $draft['name'] = wp_strip_all_tags(trim(wp_unslash($_POST['name'])));
998 if (isset($_POST['email'])) $draft['email'] = sanitize_email(trim(wp_unslash($_POST['email'])));
999 if (isset($_POST['subject'])) $draft['subject'] = sanitize_text_field(trim(wp_unslash($_POST['subject'])));
1000 if (isset($_POST['details'])) $draft['details'] = trim(wp_unslash($_POST['details']));
1001 if (isset($_POST['meta'])) $draft['meta'] = trim(wp_unslash($_POST['meta']));
1002
1003 // Include template (variables $draft and $notice are in scope for the include).
1004 include_once dirname(dirname(__FILE__)).'/menu-pages/get-help-panel.inc.php';
1005 }
1006
1007 /**
1008 * Sends the Get Help email (simple contact form, Bcc user).
1009 *
1010 * @package s2Member\Menu_Pages
1011 * @since 250824
1012 *
1013 * @return array Array like ['ok' => bool, 'msg' => string].
1014 */
1015 public static function get_help_mail() {
1016 if (!current_user_can('manage_options')) {
1017 return array('ok' => false, 'msg' => __('You do not have permission to send support requests from this page.', 's2member'));
1018 }
1019
1020 $name = isset($_POST['name']) ? wp_strip_all_tags(trim(wp_unslash($_POST['name']))) : '';
1021 $email = isset($_POST['email']) ? sanitize_email(trim(wp_unslash($_POST['email']))) : '';
1022 $subject_in = isset($_POST['subject']) ? sanitize_text_field(trim(wp_unslash($_POST['subject']))) : '';
1023 $details = isset($_POST['details']) ? trim(wp_unslash($_POST['details'])) : '';
1024 $meta = isset($_POST['meta']) ? trim(wp_unslash($_POST['meta'])) : '';
1025
1026 $email = str_replace(array("\r", "\n"), '', $email); // header-injection safety
1027 if (empty($email) || !is_email($email)) {
1028 return array('ok' => false, 'msg' => __('Please provide a valid email address.', 's2member'));
1029 }
1030 if ($details === '') {
1031 return array('ok' => false, 'msg' => __('Please describe how I can help.', 's2member'));
1032 }
1033
1034 // Cap length and normalize body fields
1035 $max_len = 100000;
1036 if (strlen($details) > $max_len) $details = substr($details, 0, $max_len);
1037 if (strlen($meta) > $max_len) $meta = substr($meta, 0, $max_len);
1038
1039 $details = str_replace(array("\r\n", "\r"), "\n", $details);
1040 $meta = str_replace(array("\r\n", "\r"), "\n", $meta);
1041 $message = $details."\n\n".$meta;
1042
1043 // Subject with fallback/prefix
1044 $subject = (string) apply_filters('ws_plugin__s2member_get_help_subject', $subject_in);
1045 $subject = str_replace(array("\r\n", "\r", "\n"), ' ', trim($subject));
1046 $subject = ($subject === '') ? __('s2Member help request', 's2member') : 's2Member help: '.$subject;
1047
1048 // Sanitize display names
1049 $name_hdr = str_replace(array("\r\n", "\r", "\n"), ' ', (string) $name);
1050 $name_hdr = str_replace('"', "'", $name_hdr);
1051
1052 $sitename = c_ws_plugin__s2member_utils_urls::parse_url(home_url(), PHP_URL_HOST);
1053 $sitename = is_string($sitename) ? strtolower($sitename) : '';
1054 if (strpos($sitename, 'www.') === 0) $sitename = substr($sitename, 4);
1055
1056 $default_from_email = 'wordpress@'.$sitename;
1057 $default_from_name = ($name_hdr !== '' ? $name_hdr : get_bloginfo('name'));
1058
1059 // Honor WP and s2 filters, then cleanup
1060 $from_email = (string) apply_filters('wp_mail_from', $default_from_email);
1061 $from_name = (string) apply_filters('wp_mail_from_name', $default_from_name);
1062
1063 $from_email = (string) apply_filters('ws_plugin__s2member_get_help_from_email', $from_email);
1064 $from_name = (string) apply_filters('ws_plugin__s2member_get_help_from_name', $from_name);
1065
1066 $from_email = str_replace(array("\r", "\n"), '', $from_email);
1067 $from_name = str_replace(array("\r\n", "\r", "\n"), ' ', $from_name);
1068 $from_name = str_replace('"', "'", $from_name);
1069
1070 // Build headers
1071 $from_line = '"'.$from_name.'" <'.$from_email.'>';
1072 $user_line = ($name_hdr !== '' ? '"'.$name_hdr.'" ' : '').'<'.$email.'>';
1073
1074 $headers = array('Content-Type: text/plain; charset=UTF-8');
1075 $headers[] = 'From: '.$from_line;
1076 $headers[] = 'Reply-To: '.$user_line;
1077 $headers[] = 'Bcc: '.$user_line;
1078 $headers[] = 'X-s2Member-Help: 1';
1079
1080 $headers = apply_filters('ws_plugin__s2member_get_help_headers', $headers, compact('name','email','subject','details','meta'));
1081
1082 // Recipient
1083 $to = (string) apply_filters('ws_plugin__s2member_get_help_mailbox', 'support@wpsharks.com');
1084 if (empty($to) || !is_email($to)) {
1085 return array('ok' => false, 'msg' => __('Support mailbox is not configured correctly.', 's2member'));
1086 }
1087
1088 $sent = wp_mail($to, $subject, $message, $headers);
1089
1090 if (!$sent) {
1091 if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
1092 error_log('s2Member Get Help: wp_mail() returned false. To='.$to.' | Subject='.$subject);
1093 }
1094 return array('ok' => false, 'msg' => __('Email could not be sent (mail server issue). Please try again or contact us directly.', 's2member'));
1095 }
1096 return array('ok' => true, 'msg' => __('Thanks — your message was sent. You’ll receive a copy by email.', 's2member'));
1097 }
1098 }
1099 }
1100